mtb-routes
Suggests running a local BRouter routing server via the official Docker image to avoid public server limits.
Exports generated GPX routes to Garmin via an installed Garmin MCP server's upload_course tool, e.g. uploading courses with activity_type mountain_biking.
Mentions that Komoot's API is only available to partners under contract, so routes must be imported manually on the Komoot website.
Routes MTB tracks on real OpenStreetMap trails via BRouter or OpenRouteService; geocodes places with Nominatim and checks protected areas through the Overpass API, all built on OSM data.
Notes that Strava's API cannot create routes, so generated GPX files must be imported manually on the Strava website.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mtb-routesmake me a 25 km MTB loop from El Papiol on dirt tracks"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Bicycle routes generator MCP
English Β· EspaΓ±ol
β¬οΈ Download the Claude Desktop extension (bicycle-routes-generator-mcp.mcpb)
Generates mountain bike and road cycling routes from a location and saves them as GPX with elevation. The track is computed by a routing engine (BRouter or OpenRouteService) on real OpenStreetMap trails and roads: neither the AI nor the application invents coordinates.
You can use it:
With Claude (Claude Desktop or Claude Code) through a local MCP server: "make me a ~25 km MTB loop from El Papiol, little climbing, on dirt tracks".
From the terminal, without AI:
bicycle-routes generate "El Papiol" --km 25 --type tracks.
Every route comes with a summary (distance, climbing, maximum grade, % of pavement, dirt track and singletrack) and warnings: sections with motor traffic, grades over 12 %, protected areas, ways where bikes are not allowed and technical sections.
πΎ GPX files go to
/tmp/bicycle-routesby default. macOS empties/tmpon restart and cleans up old files, so copy the routes you want to keep (or setBIKE_GPX_DIRto another folder).
β οΈ Always check the route on a map before riding. OpenStreetMap data may be incomplete or out of date, and does not reflect temporary closures or every local regulation. Climbing is computed from a digital elevation model (~90 m) and may differ from what a barometric watch or bike computer records (often by 10-20 %); short ramps may be smoothed out.
Easy install in Claude Desktop (.mcpb extension)
Download
bicycle-routes-generator-mcp.mcpb(always the latest release), or build it withnpx @anthropic-ai/mcpb pack . dist/bicycle-routes-generator-mcp.mcpb.Double-click the file (or drag it into Claude Desktop β Settings β Extensions) and click Install.
Fill in the configuration: the GPX folder (
/tmp/bicycle-routesby default) and a contact email (required by the OpenStreetMap/Nominatim usage policy). The other fields are optional.Ask Claude: "make me a 25 km MTB loop from El Papiol on dirt tracks".
No need to install Python: this is a uv extension, so Claude Desktop downloads Python and the
dependencies automatically the first time (it can take a minute). .mcpb is the successor of
the .dxt format; older Claude Desktop versions that only support .dxt cannot run uv
extensions.
Related MCP server: Valhalla MCP Server
Manual install (CLI and Claude Code)
Requirements: macOS, Windows or Linux with uv (it installs Python for you).
git clone <repository-url> bicycle-routes-generator-mcp
cd bicycle-routes-generator-mcp
uv sync
cp .env.example .env # and edit itConfiguration (environment variables)
There are no keys or personal data in the code: everything is configured through environment
variables (or a .env file in the folder you run the CLI from).
Variable | Default | Purpose |
|
| Folder where GPX files are saved. The app only writes here (and to its |
| β | Email or URL sent in the User-Agent. Recommended: required by Nominatim's policy. |
|
|
|
|
| BRouter server (public or local, see below). |
| β | Free OpenRouteService key if |
|
| Check protected areas with the Overpass API. |
| β | Only for |
|
| Where to keep the cache and index. |
Command-line usage
uv run bicycle-routes generate "El Papiol" --km 25 --type tracks --max-climb 500
uv run bicycle-routes generate "41.4386,2.0108" --km 15 # coordinates work too
uv run bicycle-routes generate "El Papiol" --km 20 --end "Sant Cugat del Vallès" # point-to-point
uv run bicycle-routes generate "El Papiol" --km 30 --via "Castellbisbal" --via "Molins de Rei" # loop through via points
uv run bicycle-routes generate "El Papiol" --km 40 --seed 7 # another reproducible alternative
uv run bicycle-routes generate "El Papiol" --km 25 --json # JSON output
uv run bicycle-routes listTrail types (--type):
tracks: favours wide dirt tracks, little singletrack and no extreme ramps.mixed(default): general MTB.singletrack: more singletrack, less pavement.road: road bike. Paved roads with little traffic; warns about unpaved sections and only about busy roads (main roads or heavy estimated traffic), not ordinary secondary roads.
Real example (abridged):
MTB El Papiol 26 km loop
Distance: 25.9 km Β· Climbing: +442 m / -443 m Β· Elevation 20β266 m
Max grade: 17.0 % uphill, 18.0 % downhill
Surface: paved 18.7 % Β· track 67.3 % Β· singletrack 14.1 %
GPX: /tmp/bicycle-routes/mtb-el-papiol-26-km-loop-20261017-121732.gpx
Warnings:
- [traffic] 1.2 km on roads with motor traffic in 6 section(s); ride carefully.
- [grade] 8 sections (about 2.2 km) are steeper than 12 %; listing the 6 steepest.
- [protected_area] The route crosses the protected area Β«Parc Agrari del Baix LlobregatΒ»β¦More examples with real output in docs/pruebas-reales.md (in Spanish).
Using it with Claude (MCP server)
The server exposes two tools:
generate_route(start, distance_km, max_climb_m?, trail_type?, loop=true, end?, name?, seed?, via?)β GPX path, summary and warnings.viais a list of places the route must pass through, in order (e.g. "a loop from El Papiol passing through Castellbisbal and Molins de Rei").list_routes()β saved routes.
Claude Code
claude mcp add bicycle-routes-generator-mcp \
-e BIKE_GPX_DIR=/tmp/bicycle-routes -e BIKE_CONTACT=you@example.com \
-- uv run --directory /path/to/bicycle-routes-generator-mcp bicycle-routes-mcpClaude Desktop (without the extension)
The .mcpb extension is the easiest option (see above). To configure it by hand instead:
Settings β Developer β Edit config (claude_desktop_config.json):
{
"mcpServers": {
"bicycle-routes-generator-mcp": {
"command": "/absolute/path/to/uv",
"args": ["run", "--directory", "/path/to/bicycle-routes-generator-mcp", "bicycle-routes-mcp"],
"env": {
"BIKE_GPX_DIR": "/tmp/bicycle-routes",
"BIKE_CONTACT": "you@example.com"
}
}
}
}Use the absolute path to uv (which uv on macOS/Linux, where uv on Windows), because
Claude Desktop does not always inherit the terminal's PATH. Restart Claude Desktop.
Building the extension
manifest.json describes the extension (uv type, tools and configuration fields) and
.mcpbignore keeps the tests, docs and your .env out of the package.
npx @anthropic-ai/mcpb validate manifest.json
npx @anthropic-ai/mcpb pack . dist/bicycle-routes-generator-mcp.mcpb
python scripts/make_icon.py # only if you want to regenerate icon.pngPublishing a release
Bump
versioninmanifest.json,pyproject.tomlandsrc/bicycle_routes/__init__.pytogether.Create and push a matching tag:
git tag v0.1.0 git push origin v0.1.0The
ReleaseGitHub Actions workflow runs the tests, buildsbicycle-routes-generator-mcp.mcpband attaches it to a new GitHub Release. The download link at the top of this README always points to the latest one.
Garmin, Wahoo and other platforms
The application is platform-independent: the output is a standard GPX file. Sending to
platforms is done by optional exporters sharing a common interface
(export(route, config) -> ExportResult, in src/bicycle_routes/exporters/).
disk (default): saves the GPX in
BIKE_GPX_DIR.garmin: delegates to an already installed Garmin MCP server (
upload_coursetool).With Claude (recommended): nothing to configure. Ask "upload it to Garmin" and Claude will call the Garmin MCP server's
upload_coursewith the generated GPX andactivity_type="mountain_biking"("road_biking"for road routes).From the CLI: set
BIKE_GARMIN_MCP_COMMANDto the command that starts that server and use--garmin. Garmin credentials are handled by that server, never by this app.Community Garmin MCP servers use Garmin Connect's unofficial web API and may stop working. The official API (Courses API) is only available to approved businesses.
wahoo: not implemented. The Cloud API has
POST /v1/routes, but you must request an app and get it approved (notes inexporters/wahoo.py).Komoot (API only for partners under contract) and Strava (its API cannot create routes): import the GPX manually on their websites.
Routing engines
BRouter (default, MIT, no key). Uses the
mtbprofile withStrictNOBicycleaccess=1(avoids ways taggedbicycle=no) andavoid_unsafe=1; forroad, thefastbikeprofile withconsider_traffic=1(the same routes asfastbike-lowtraffic). The publicbrouter.deserver makes no availability guarantees: the app sends at most 1 request per second and caches responses. For heavy use, run a local one (not yet tested in this project):mkdir -p segments4 curl -o segments4/E0_N40.rd5 https://brouter.de/brouter/segments4/E0_N40.rd5 # Catalonia docker run -d -p 17777:17777 -v "$PWD/segments4:/segments4" ghcr.io/abrensch/brouter # and in .env: BIKE_BROUTER_URL=http://localhost:17777OpenRouteService (optional):
BIKE_ENGINE=orsandORS_API_KEY(free plan: 2,000 routes/day, 40/min). Profilecycling-mountain, orcycling-roadforroad.
How the loop is generated
The place is geocoded with Nominatim (or the coordinates you give are used).
3-4 waypoints are placed on a circle passing through the start, with a random orientation (reproducible with
--seed).The engine routes start β waypoints β start on real trails.
The radius is adjusted until the requested distance is approached (about 2-4 requests).
Six candidates are generated and the best one is picked by distance, traffic, repeated trails, surface, forbidden access, climbing and grades.
With via points (--via / via), your points replace the random ones: the engine routes
start β via 1 β β¦ β via N β start (or β end, for point-to-point), in the given order. The
distance comes from those points, not from --km; a warning is added if it differs by more
than 15 %.
External services and their policies
Nominatim (place lookup): usage policy. At most 1 request/second, an identifying User-Agent, cached results, no autocomplete or bulk queries. For heavy use, run your own Nominatim.
Overpass API (protected areas): about 10,000 requests/day per IP as a guideline; one cached query per route.
brouter.de: public server without guarantees; use a local one for heavy use.
Development
uv run pytest # tests with simulated engine, geocoder and areas (no network)
uv run pytest -m network # optional test against the real servicesLayout:
src/bicycle_routes/
βββ core/ # models, loop generator, analysis and GPX (no network, no platforms)
βββ engines/ # BRouter and OpenRouteService (RoutingEngine interface)
βββ geo/ # Nominatim and protected areas (Overpass)
βββ exporters/ # disk, garmin (via MCP), wahoo (pending)
βββ net.py # HTTP client: User-Agent, rate limiting, retries and cache
βββ service.py # reusable orchestration (CLI, MCP and a future remote server)
βββ mcp_server.py # MCP server (stdio)
βββ cli.pyThe core does not depend on the CLI or the MCP server, so it could later be offered as a remote server (HTTP transport) by changing only the exporter and the transport.
License and attribution
Code under the MIT license. See NOTICE.
Trail and place data Β© OpenStreetMap contributors, under the ODbL license. Generated GPX files include this attribution in their metadata; keep it if you share them. BRouter elevation: SRTM (CGIAR-CSI).
Available Tools
2 toolsgenerate_routeA
Generate an MTB or road cycling route on real trails/roads, save it as GPX and return the summary and warnings.
Takes 10 to 60 seconds (it evaluates several candidates and picks the best one).
| Name | Required | Description | Default |
|---|---|---|---|
| end | No | End point if the route is not a loop | |
| via | No | Places the route must pass through, in order (names or 'lat,lon') | |
| loop | No | Return to the start point | |
| name | No | Route name (optional) | |
| seed | No | Seed to repeat the same route; change it for an alternative | |
| start | Yes | Start location: place name ('El Papiol, Barcelona') or 'lat,lon' | |
| trail_type | No | tracks = wide, non-technical dirt tracks; mixed = general MTB; singletrack = more singletrack; road = road bike, paved roads with little traffic | mixed |
| distance_km | Yes | Approximate distance in km | |
| max_climb_m | No | Maximum desired climbing in metres |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does meaningful work: it discloses a file side effect (saves a GPX), the return payload (summary and warnings), a latency range, and non-determinism ('evaluates several candidates and picks the best one'). It omits auth/permission needs and whether an existing GPX is overwritten, but the disclosed behavior goes well beyond the name.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, zero filler. The core capability is front-loaded and the timing advisory is correctly placed as a secondary parenthetical so an agent can decide quickly whether to call it.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values needn't be enumerated, though the description helpfully notes summary and warnings. With all 9 parameters documented in the schema and latency plus side effects covered, an agent has what it needs; only auth/overwrite behavior is unaddressed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% across all 9 parameters, so the schema already documents start/end/via/seed/trail_type semantics fully. The description's mention of MTB vs road maps loosely onto the trail_type enum but adds no syntax or format detail beyond the schema, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource plus the full action chain: generate a cycling route, save it as GPX, return summary and warnings. The generate-vs-list distinction against the sole sibling list_routes is implicit rather than named, so it falls just short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No explicit when-to-use or when-not-to-use guidance, and the sibling list_routes is never referenced as an alternative. The stated 10-60 second runtime implicitly signals this is an expensive operation to reserve for actual route creation, which is the only usage cue present.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_routesA
List the saved GPX routes (most recent first) with their summary.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations at all, the description carries the full burden, and it does add one meaningful behavioral fact: results are ordered most recent first. However, it says nothing about pagination or result limits, and does not state that it is a read-only operation. Output schema exists, so return-value shape is covered elsewhere.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single tight sentence that front-loads the verb and resource and packs in ordering and summary detail with zero waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a parameterless list tool with an output schema defining the return shape, the description is nearly sufficient. The only meaningful omission is any hint about result volume or truncation behavior for long route lists.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is no parameter semantics to document and the baseline of 4 applies. The description correctly adds no parameter information because none exists.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (List) and resource (saved GPX routes) plus a scope detail (most recent first) and return shape (with their summary). It is clear enough to distinguish from generate_route, though it does not name that sibling explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: an agent infers this is the tool for viewing existing routes as opposed to generating a new one. There is no explicit when-to-use statement, no exclusions, and no named alternative to redirect to.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
v0.4.0- First observed
generate_route - First observed
list_routes
TDQS
Scored across 2 tools
The two tools have clearly distinct purposes: one creates a new route, the other lists existing saved routes. There is no overlap that could cause misselection.
Both names follow a clean verb_noun snake_case pattern (generate_route, list_routes). The singular/plural noun difference is a natural consequence of the operation, not an inconsistency.
Two tools is thin for a route-management server; only creation and listing are exposed. It is defensible for a narrow generation utility but sits at the borderline of the 1-2 tool range.
Create and list are covered, but there is no get (retrieve a route's details/GPX), delete, or export tool, leaving notable gaps for managing saved routes. An agent can work around this only via the filesystem, which is fragile.
Maintenance
Related MCP Connectors
Location tools for Claude and other MCP clients, strongest in the US and Great Britain. Geocode whole lists with a confidence score per row, filter by distance, get road distances and drive-time areas, and plan multi-driver routes with Google Maps links. Check flood zones, crime and live warnings, find nearby places and UK registers (NHS, CQC, food hygiene), and keep shareable maps. Reads and writes Google Sheets. Free: sign in with Google.
Turn Claude or ChatGPT into a cycling coach that plans your week, grades it, and adapts. Free beta.
Connect Claude to your Intervals.icu watch data for fitness, workout review, and plan writing.
Analyse cycling-event GPX routes and build bounded, practical ride-fuelling carry plans.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAn AI-powered companion that provides access to the RideWithGPS API, allowing you to interact with cycling routes, trips, events, and user data through natural language.8MIT
- FlicenseNot gradedqualityDmaintenanceIntegrates Claude Desktop with the Valhalla routing engine to provide OpenStreetMap-based routing and isochrone capabilities. It enables users to calculate travel paths and reachability polygons for various transportation modes using natural language.5-
- FlicenseAqualityCmaintenanceEnables planning bike routes using the BRouter engine, with support for waypoints, route profiles, and exporting routes as GPX or other formats.51-
- AlicenseNot gradedqualityCmaintenanceEnables Claude to read recent training activities and wellness data, then create, update, or delete structured workouts through intervals.icu so they sync to Garmin Connect and a watch.MIT