bosch-ebike-connect-classic
Provides tools for interacting with a Bosch eBike Connect Classic account, including retrieving profile details, e-bikes, ride statistics and activities, route details and exports (GPX/TCX/CSV), saved places, weather forecasts, dashboard feed, integration status, settings, licenses, and disconnecting the account.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
bosch-ebike-connect-classic
Remote MCP server for the Bosch eBike Connect Classic portal (ebike-connect.com). Connect your account to Claude, ChatGPT, Cursor or any MCP client and ask about your rides, statistics and bikes. Multi-user (everyone signs in with their own account), OAuth 2.1, one Docker container.
Unofficial. Not affiliated with Bosch. It uses the portal's own JSON endpoints, which can change without notice, and Bosch's terms may restrict automated access. Use at your own risk. Classic system only (ebike-connect.com), not the Smart System / eBike Flow app.
Developers and contributors: everything technical (architecture, tests, security rules, releases, portal API notes) is in AGENTS.md. This README is for people who run and use the server.
Quick start
Run the published image. You need Docker with Compose and one file in an empty folder.
1. docker-compose.yml
services:
bosch-ebike-connect-classic:
image: ghcr.io/edocsyl/bosch-ebike-connect-classic:1.0.0
user: "1000:1000" # owner of ./config (run `id -u` / `id -g`)
ports:
- "127.0.0.1:8080:8080"
environment:
PUBLIC_URL: http://localhost:8080 # address your clients use (https://... when public)
# TRUST_PROXY: "xff" # behind a reverse proxy ("cloudflare" for a Cloudflare tunnel)
# LOG_LEVEL: info # debug | info | warning | error
# GATEWAY_SECRET: ... # optional: else generated into ./config/gateway.secret
volumes:
- ./config:/data # everything the server keeps: database + secret
restart: unless-stopped
read_only: true
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
mem_limit: 512m
pids_limit: 2002. Start
mkdir -p config # create it yourself, so it belongs to you and not to root
docker compose up -d
curl http://localhost:8080/healthz # -> ok3. Connect a client, for example Claude Code:
claude mcp add --transport http bosch-ebike-connect-classic http://localhost:8080/mcp
# then in Claude Code: /mcp -> bosch-ebike-connect-classic -> AuthenticateSign in with your eBike Connect e-mail and password in the browser, then ask: "Show my e-bikes" or "When was my last ride?"
The image is private until you make it public (GitHub → Packages → Package settings → Visibility), or log in first:
echo "$TOKEN" | docker login ghcr.io -u <github-user> --password-stdin(token withread:packages). To build it yourself instead: clone this repository and rundocker compose up -d --build.
Related MCP server: mcp-garmin
Connect clients
The server URL is <PUBLIC_URL>/mcp; the landing page at <PUBLIC_URL>/ shows it.
Client | How |
Claude (web, desktop) | Settings → Connectors → Add custom connector → |
Claude Code |
|
ChatGPT, Cursor, VS Code, MCP Inspector, … | add a remote MCP server of type Streamable HTTP with the URL |
Standard MCP Streamable HTTP and OAuth 2.1 (dynamic client registration, PKCE S256, refresh-token rotation, RFC 9728 / 8414 / 8707 / 9207), so no client-specific setup. The legacy SSE transport is not supported.
Configuration and data
Everything is set in the environment: block of the compose file:
Variable | Default | Meaning |
|
| Address your clients use, no trailing slash. Use your https address when public. |
| generated | ≥ 32 characters. If unset, one is generated on first start and kept in |
| off |
|
|
|
|
|
|
|
| empty | Extra browser origins allowed to call |
| empty | Comma list. If set, |
| empty | Extra custom URL schemes clients may use (built in: |
| off |
|
Limits (all optional, whole numbers):
Variable | Default | Limits |
|
| sign-in attempts per IP per 10 min |
|
| failed sign-ins per e-mail per 10 min (successful ones do not count) |
|
| client registrations per IP per hour |
|
| token requests per IP per minute |
|
| MCP calls per user per minute |
|
| request body size for |
|
| request body size for |
|
| registered OAuth clients (unused ones are purged after 7 days) |
|
| largest accepted reply from the Bosch portal |
Rate limits live in memory of the one container; they reset on restart.
Logs: docker compose logs -f. LOG_LEVEL decides how much is written:
Level | Logs |
| only failures that need attention (unexpected errors) |
| plus rejected sign-ins, rate limits, foreign origins, expired Bosch sessions, invalid tokens |
| plus start-up, sign-ins, registered clients, disconnects, portal errors |
| plus every tool call, every request to the portal (path, status, duration), token requests, HTTP access log |
Logs never contain passwords, tokens, cookies or the full e-mail address (shown as a***@e***.com).
Data: the ./config folder holds everything the server keeps: gateway.sqlite3 (encrypted Bosch
sessions, OAuth clients, tokens) and gateway.secret. Back it up as a whole. Create it yourself before the
first start (otherwise Docker creates it as root and the container cannot write); user: in the compose
file must be its owner. To put the data elsewhere, change the left side of the volume, e.g.
/srv/ebike:/data. Deleting the folder (container stopped) removes all users' sessions. For a stricter
setup set GATEWAY_SECRET yourself and keep it out of ./config, so a copy of the folder alone is useless.
Updates: pin an exact version in image: (e.g. :1.0.0), change it to update, then
docker compose pull && docker compose up -d. The data in ./config is kept; go back by setting the old
version. What changed in each version: Releases.
Going public: put TLS in front (for example cloudflared tunnel --url http://localhost:8080), set
PUBLIC_URL to the https address and TRUST_PROXY (xff or cloudflare); for a public server also set ALLOWED_REDIRECT_HOSTS. You then hold other people's Bosch sessions and ride
data: provide a privacy notice (GDPR / Swiss DSG) and start with a trusted circle.
Tools
Read-only, except disconnect_account. Errors come back as Error: … with isError: true.
Tool | Returns |
| name, units, e-mail |
| drive unit, display, batteries, firmware |
| totals per year and month, plus all-time |
| trips with their rides; |
| ride or trip aggregates; |
| GPX, TCX or CSV; |
| filter by title, |
| saved routes ( |
| favourites |
| daily or hourly forecast for a latitude / longitude |
| news and latest activities incl. GPS tracks; keep |
| Strava / Komoot connection state (no tokens) |
| account settings; purchased licences |
| signs out and deletes the user's data on this server; needs |
Disconnect / remove an account
Ask the assistant to run disconnect_account with confirm=true. It sends the portal's logout request,
then deletes that user's stored session, tokens and codes. Afterwards the connector must be authorised again.
The portal does not revoke the old session on logout (verified: the cookie still worked afterwards). To really end the session at Bosch, change your password on ebike-connect.com.
As operator you can also delete one account (replace the e-mail, lower case) or everything:
docker compose exec bosch-ebike-connect-classic python -c "
import sqlite3; db = sqlite3.connect('/data/gateway.sqlite3'); k = 'you@example.com'
[db.execute(f'delete from {t} where account_key=?', (k,)) for t in ('accounts', 'tokens', 'codes')]
db.commit()"
# everything: docker compose down && rm -rf configHow it works and security
Client ──OAuth 2.1──▶ /oauth/* ── sign-in page ──▶ ebike-connect.com
Client ──POST /mcp + Bearer──▶ guard ─▶ MCP tools ─▶ ebike-connect.comThe password goes to Bosch once and is never stored. Only the session cookie is kept, encrypted with AES-256-GCM (key derived from
GATEWAY_SECRET).Tokens and authorization codes are stored only as SHA-256 hashes; refresh tokens rotate; codes are single-use; redirect URIs must match exactly.
Tools take no account parameter: the user comes from the validated token, so users cannot reach each other.
An expired Bosch session drops the tokens; the next call gets
401and the client signs in again./mcprejects foreign browser origins; sign-in, registration, token and MCP calls are rate limited.Sign-in page shows where your browser is sent afterwards and that the app name is unverified; custom URL schemes are restricted, request bodies are size-capped, rate limits are bounded and configurable.
A replayed refresh token revokes all tokens of that client and account.
The container runs as a non-root user with a read-only filesystem, no capabilities, and memory / process limits.
Known limitations
Unofficial API: a portal change can break tools. Tested live with one real Classic account, with a scripted OAuth client and with Claude Code; not with Claude Desktop, Claude web or other vendors' clients.
Not verified with real data:
get_route_details,export_route_gpx, non-emptyget_saved_placesandget_licenses(the test account has none). The expired-session path is tested with a fake Bosch only.SQLite and in-memory rate limits in one container: fine for a small group, not horizontally scalable.
Reviewed by an automated security review, not by an independent audit or penetration test. If you run it publicly, you carry the responsibility for the sessions you store.
Contributing, credits and license
Contributions are welcome: start with AGENTS.md. Endpoints found in the portal's web bundle and in ebike-connect-js. Gateway architecture inspired by missingmcp and garmin_mcp (no code shared). Licensed under the MIT License; "Bosch" and "eBike Connect" are trademarks of their owners.
This server cannot be deployed
Maintenance
Related MCP Connectors
Remote MCP server for training, nutrition, wellness, and performance data with OAuth 2.0.
One OAuth connector for your team's remote MCP servers, with Tool Packs, guardrails and logs.
Your own cycling data for your AI assistant: rides, power, climbs and routes. Read-only.
Managed MCP gateway with OAuth, per-agent permissions, shared accounts and activity logs.
Related MCP Servers
- AlicenseAqualityBmaintenanceConnects MCP clients to Garmin Connect data, enabling queries about activities, sleep, heart rate, body battery, and training status.162MIT
- AlicenseNot gradedqualityDmaintenanceExposes Garmin Connect data through MCP, enabling retrieval of activities, health metrics, training status, and workouts, with OAuth authentication and confirmed write operations.MIT
- AlicenseNot gradedqualityBmaintenanceEnables OAuth-authenticated access to Garmin account data through MCP, with a read-only web dashboard for monitoring account connections and activity.MIT
- AlicenseNot gradedqualityCmaintenanceEnables MCP clients to read and update Garmin Connect data, including activities, health metrics, training status, and more, with per-account multi-tenant authentication.3MIT