Bike Router

io.github.JonasHeinickeBioPublished by GitHub account JonasHeinickeBiov0.3.0Updated Oct 29, 2026

Plan bike routes with a self-hosted OSM service: facts only, with weather and sights.

The publisher's own server: 370.ai — AI Gateway: 100+ Chat Models, Image & Speech Generation

VerifiedSTDIODesktop onlyLocation & LifestyleDeveloper Tools

Overview

AI-generated overview

MCP bridge to a self-hosted pyBikeRouter API that plans OSM-based bike routes with weather, sights and GeoJSON/GPX exports.

What you can do
  • Plan cycling routes from place names or coordinates, with bike type and constraints
  • Get scored route alternatives with distance, elevation and warnings, plus a facts-only explanation
  • Export routes as GeoJSON or GPX and look up sights near a route
  • Add a weather forecast along the route at a departure time
  • Ask for clarification when a place name is ambiguous instead of guessing
When to use it
Useful when you already run the pyBikeRouter service and want an assistant to plan, compare and explain cycling routes, including weather and sightseeing stops, from a chat or agent workflow.
Requirements
A local process started with uvx from the bike-router PyPI package, plus a running pyBikeRouter API whose address is set in BIKE_ROUTER_API_URL. The API itself typically needs an openrouteservice API key (ORS_API_KEY) and network access for geocoding.
Before you install
The server only talks to the API at BIKE_ROUTER_API_URL, so point it at a trusted instance. Route plans may be recorded in the API's route history when a database is configured. Routes are not a safety guarantee and map data may be incomplete.

Installation

In SourceWeft

  1. Open Bike Router in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

bike-routing-agent

[CI]

An OSM-based bike-routing service: a typed, provider-neutral domain model, a full openrouteservice API client plus routing adapter, and a LangGraph workflow that turns a validated route request into an explainable, exportable cycling route.

The LLM (where used) may parse free text, ask clarification questions, and explain a chosen route. It never invents coordinates, geometry, elevation, or route suitability -- all of that comes from OSM-derived routing engines.

Easy install (no programming needed)

Download pybikerouter-easy-<version>.zip from the Releases page, unzip it, install Docker Desktop, and double-click the Start file for your computer. The first start asks one question -- where you ride -- downloads that area's map data, and opens the planner in your browser. Step by step, in plain words: docs/easy-install.md (and START-HERE.txt in the download).

Documentation

The full documentation suite lives in docs/:

DocumentContents
ArchitectureGraph nodes, state, branching, terminal statuses, error model
APIEndpoint contracts, response statuses, artifact serving
ConfigurationEvery environment variable, validation rules, deployment notes
ProvidersProtocols, ORS client/adapter behaviour, geocoders, BRouter/Valhalla adapters, adding a backend
Backend comparisonMeasured ORS vs BRouter behaviour per bike type, combining both engines
GeocodingNominatim vs Pelias, confidence/ambiguity semantics, tuning
Scoring & exportsScore math, uncertainty policy, explanation rules, GeoJSON/GPX
Plan from wordsDescribe a ride in a sentence; an optional LLM turns it into the structured request (never coordinates)
WeatherForecast along the route at your departure time: temperature, rain, wind vs. riding direction (free, keyless providers)
Self-hosted stackRun routing and/or geocoding locally from one OSM extract; hardware requirements and small-PC options
TestingTest layout, mocking conventions, live tests, CI
Web plannerPutting places into the trip; click a point on the map and choose where in the route it goes
iPhone / TailscaleUse the planner from an iPhone as a home-screen app over your tailnet
RoadmapPlanned work and explicit non-goals

Architecture

FastAPI  |  vLangGraph RouteAgentState  |  +--> parse_request        (structured input first; LLM extraction is an injectable dependency)  +--> validate_request      (Pydantic)  +--> geocode_locations     (Nominatim by default, public or self-hosted; ambiguity -> clarification, never a guess)  +--> route_with_provider   (openrouteservice; engine-neutral RoutingRequest in, RouteCandidate out)  +--> score_candidates      (deterministic distance/elevation/warning scoring)  +--> explain_and_export    (facts-only explanation + GeoJSON/GPX export)  |  vJSON response + GeoJSON/GPX artifacts

Graph branches:

START -> parse_request      -> validate_request -> (invalid | awaiting_clarification) -> END      -> geocode_locations -> (awaiting_clarification | provider_failure) -> END      -> route_with_provider -> (no_route | provider_failure) -> END      -> score_candidates -> (no_route) -> END      -> explain_and_export -> END

Every path ends with an explicit status; the graph never calls a routing provider unless validation and geocoding both succeeded, and it never returns without a terminal status.

Setup

Requires Python 3.11+ and Poetry.

bash
poetry installcp .env.example .env

poetry install creates an in-project virtualenv (.venv/, see poetry.toml) with both runtime and dev dependencies. Run any command inside it with poetry run <command>, or poetry shell to activate it directly.

Edit .env and set ORS_API_KEY to a valid openrouteservice API key.

Environment variables

VariablePurposeDefault
ORS_API_KEYopenrouteservice API key(required for live routing)
ORS_BASE_URLopenrouteservice base URLhttps://api.openrouteservice.org
ORS_TIMEOUT_SORS request timeout (seconds)10.0
ORS_MAX_RETRIESRetries for timeouts/5xx2
GEOCODER_PROVIDERGeocoder backend: nominatim (public or self-hosted) or pelias (needs a Pelias API behind ORS_BASE_URL, see docs/geocoding.md; refuses the public ORS URL at startup)nominatim
GEOCODER_BASE_URLNominatim base URL (ignored when GEOCODER_PROVIDER=pelias)https://nominatim.openstreetmap.org
GEOCODER_TIMEOUT_SGeocoder request timeout5.0
GEOCODER_USER_AGENTRequired by Nominatim's usage policybike-routing-agent/0.1
GEOCODER_CACHE_TTL_SIn-memory geocode cache TTL3600
GEOCODER_AMBIGUITY_MARGINConfidence gap below which top-2 results are "ambiguous"0.05
GEOCODER_MIN_CONFIDENCEMinimum confidence to auto-accept a top result0.3
VALHALLA_BASE_URLBase URL of a self-hosted Valhalla (meili) serverhttp://localhost:8002
VALHALLA_TIMEOUT_SPer-request Valhalla timeout (seconds)30.0
VALHALLA_MAX_RETRIESRetries for Valhalla timeouts/5xx1
EXPORT_DIRLocal directory for GeoJSON/GPX artifactsexports
DATABASE_URLPostgreSQL/PostGIS URL: records route history, enables /v1/history/* (docs/persistence.md)unset
ARTIFACT_BACKENDlocal or database (artifacts stored in PostgreSQL)local
LOG_LEVELLog levelINFO

Running the API

bash
poetry run uvicorn bike_routing_agent.api:app --reload

The app also serves a built-in web UI (map-based route planner) at http://localhost:8000/, with the interactive API docs at /docs.

Command-line interface

Installing the package exposes a bike-router command (also runnable as python -m bike_routing_agent.cli) organised into subcommand groups:

bash
bike-router route plan --origin "Berlin Hbf" --destination "Potsdam" \    --bike-type gravel --target-distance-km 30 --output plan.jsonbike-router serve start --port 8000bike-router docker up            # docker compose -f docker/compose.yaml up -d --buildbike-router docker logs -fbike-router config show          # effective settings, secrets redactedbike-router config check         # validate settings against the environmentbike-router providers list       # provider names and bike-type -> ORS profile mapbike-router profile settings --bike-type touring   # what a request may tune (route plan --profile-param), from the LinkML schemabike-router route plan ... --format text --gpx best.gpx   # alternatives, sight stops, exportsbike-router route show plan.json # a saved plan again; `route export` writes any alternativebike-router poi along --route plan.json   # sights near a route, ranked by famebike-router brouter needed|info|download  # BRouter map tiles (asks before downloading)bike-router history list|show|stats       # past plans (needs DATABASE_URL)bike-router status show          # readiness of every component and the active featuresbike-router chat start           # plan routes by chatting (docs/chat.md)

Everything the API and the web form do is available from the CLI -- see docs/cli.md.

route plan --text "50 km gravel loop from Braunschweig" uses the optional LLM parser (docs/llm-parser.md).

route plan accepts lat,lon pairs or place text for --origin, --destination and repeated --via waypoints, prints the JSON result and exits 0 on ready, 1 on clarification/provider failure, 2 on usage errors. With DATABASE_URL set it also records the plan in the route history (best effort; plan_id in the output, --no-record to skip), a forecast along the route at --departure-time (docs/weather.md), and bike-router db migrate / db status manage the schema (docs/persistence.md).

Running with Docker

All container assets live in docker/:

bash
cp .env.example .env        # set ORS_API_KEYdocker compose -f docker/compose.yaml up --build

Open http://localhost:8000/ for the web UI. Route artifacts are stored in the named pybikerouter-exports volume. See docs/self-hosted.md for the optional local routing / geocoding stack -- including what it needs and how to run just one half on a small machine.

Example request

bash
curl -X POST http://localhost:8000/v1/route/plan \  -H 'Content-Type: application/json' \  -d '{    "origin": "Braunschweig Hauptbahnhof",    "destination": "Wolfenbüttel",    "constraints": {      "bike_type": "gravel",      "avoid_high_traffic_roads": true,      "max_ascent_m": 500    }  }'

Example response

json
{  "status": "ready",  "route": {    "provider": "ors",    "provider_profile": "cycling-regular",    "geometry_geojson": {"type": "LineString", "coordinates": []},    "metrics": {      "distance_m": 12500,      "duration_s": 2700,      "ascent_m": 85,      "descent_m": 90,      "surface_coverage": {},      "unknown_surface_fraction": null    },    "score": 0.91,    "score_breakdown": {"distance_fit": 1.0, "elevation_fit": 1.0, "warning_penalty": 0.0},    "warnings": [],    "provenance": {"provider": "ors", "profile": "cycling-regular"}  },  "candidates": [    {"provider": "ors", "provider_profile": "cycling-regular", "score": 0.91},    {"provider": "brouter", "provider_profile": "trekking", "score": 0.84}  ],  "explanation": "...",  "artifacts": {    "geojson_url": "/v1/routes/<id>.geojson",    "gpx_url": "/v1/routes/<id>.gpx"  },  "clarification": [],  "errors": []}

If origin/destination are ambiguous or unresolved, status is awaiting_clarification and clarification lists the candidate places to choose from -- no route is generated from a guess.

Bike types

constraints.bike_type selects the routing profile per engine and defaults to gravel. Supported values: road, gravel, touring, mountain, city, ebike, commuter (fast, low-traffic), recumbent. ORS ships four cycling profiles, so e.g. ebike uses cycling-electric while commuter rides on cycling-regular; the self-hosted BRouter provider differentiates further -- gravel, touring, mountain, city, commuter and recumbent each get their own profile, while road and ebike share fastbike (BRouter has no e-assist cost model) (see docs/configuration.md).

Make targets

make lists the everyday commands; they use the project's .venv (or the active environment):

bash
make install       # poetry installmake hooks         # the git hooks (commit: lint + types; push: the whole suite)make run           # the API with auto-reload (HOST=127.0.0.1 PORT=8000)make check         # lint + types + tests without coverage, stops at the first failuremake ci            # what CI runs: lint, types, tests with the 80 % coverage floormake test-fast ARGS="tests/nodes -k geocode"     # a part of the suite (no coverage floor)make deploy        # build and restart the local container (bike-router deploy up)

Linting and type checking

bash
poetry run ruff check .poetry run mypy src

Both run in CI on every push and pull request (see .github/workflows/ci.yml).

Running tests

bash
poetry run pytest

Coverage is collected on every run and the suite fails below 80% (--cov-fail-under=80 in tool.pytest.ini_options.addopts; currently 97% total, every module ≥84%).

Live tests that hit real external services are marked @pytest.mark.live and excluded by default (see tool.pytest.ini_options.addopts in pyproject.toml). Run them explicitly with:

bash
poetry run pytest -m live

Pre-commit hooks

Quality gates run automatically before each commit/push via pre-commit. Install both git hook stages once after poetry install:

bash
poetry run pre-commit install --hook-type pre-commit --hook-type pre-push

On commit it runs whitespace/EOF/YAML/TOML/merge-conflict/large-file checks, ruff check --fix and mypy src; the full pytest suite runs only in the pre-push stage on git push (mirroring CI). Run the commit stage manually with poetry run pre-commit run --all-files and the pre-push stage with poetry run pre-commit run --hook-stage pre-push --all-files.

Known limitations

  • OSM tag completeness varies by region; surface/access metadata is frequently missing and is represented as uncertainty, not treated as favorable.
  • Routing engines (ORS, Valhalla, BRouter) map bike categories to their own profiles differently; the mapping in config.py is an approximation, especially for gravel riding.
  • Elevation data (ascent/descent) varies in accuracy between providers and is not always available.
  • No route produced by this service is a safety guarantee. Explanations use hedged language ("better aligned with available map metadata") rather than claims like "safe route".
  • BRouter and Valhalla are wired up end to end but only against self-hosted deployments (ROUTING_PROVIDER=brouter / valhalla / all, BRouter via the compose profile in docker/brouter/README.md), and openrouteservice remains the default engine.
  • The Pelias geocoder option needs a Pelias API served at <ORS_BASE_URL>/pelias/v1: neither the public api.openrouteservice.org nor a stock self-hosted ORS provides one, and the adapter has only been tested against mocks (see docs/geocoding.md). For a local geocoder use the self-hosted Nominatim.

Repository layout

src/bike_routing_agent/├── api.py            FastAPI app (POST /v1/route/plan)├── config.py          Settings + provider profile mapping├── models.py          Pydantic v2 domain models├── state.py            LangGraph RouteAgentState├── graph.py            LangGraph wiring├── errors.py           Structured provider/geocoding errors├── providers/         GeocodeProvider / RoutingProvider adapters│                      (ors_client.py: full ORS client -- directions, export,│                       isochrones, matrix, snap, POIs, Vroom, elevation,│                       Pelias geocoding, health; ors.py: the RoutingProvider;│                       geocoder.py: Nominatim; pelias.py: Pelias on│                       self-hosted ORS)├── nodes/              One module per graph node├── scoring/            Deterministic route scoring├── exporters/          GeoJSON / GPX export├── frontend/           Static web UI served at /└── cli/                bike-router command line (route/serve/docker/config/providers)docker/                 Dockerfile + compose.yamltests/├── fixtures/           Sample provider responses├── nodes/               One test module per graph node├── providers/          Adapter unit tests (mocked HTTP)├── graph/              Graph branch tests├── exporters/          Export format tests└── live/                Tests marked @pytest.mark.livedocs/                   Documentation suite (see docs/README.md)

Licence

MIT, see LICENSE.

Third-party licences

BRouter (MIT) and the OpenStreetMap data (ODbL) this project builds on are credited, with the full BRouter licence text, in THIRD_PARTY_LICENSES.md.

Source: README.md at commit bc2d1da

Tools

0
SourceWeft has not started this package in its sandbox yet, so its tools are not known.

Version history

1
  1. v0.3.0LatestOct 29, 2026

Score

50% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts

    Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.

  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listed15 pts

    Answer tools/list without a credential, or ship a package that starts in a sandbox without one.

  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts

Other Router integrations