Everything on this site is read-only and free to query. Units are metric (kg, mm, Wh, Nm);
a null value means the brand doesn't state it. Prices are indicative US prices in USD.
MCP server
AI assistants can query the database through the Model Context Protocol. The endpoint needs no login:
https://emtb.pigeonstorm.com/mcp
Transport: streamable HTTP, stateless, JSON responses (POST only).
Connect
Claude (claude.ai, desktop, mobile): Settings → Connectors → Add custom connector, and paste the URL above.
Claude Code:
claude mcp add --transport http emtb-specs https://emtb.pigeonstorm.com/mcp
Any other client - a plain JSON-RPC call works too:
curl -s https://emtb.pigeonstorm.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"search_bikes",
"arguments":{"max_weight_kg":20,"sort_by":"price","limit":5}}}'
Tools
| Tool | What it does |
|---|---|
search_bikes | Find bikes. Optional filters, combined with AND: query (text in brand, model, motor),
brand, motor_brand, frame_material (carbon | carbon_front_alloy_rear | alloy),
assist_category (light | full_power), max_weight_kg, min_battery_wh,
min_torque_nm, model_year, min_price_usd, max_price_usd,
shifting (electronic | mechanical), color_screen (true), theft_protection (true: the display unit has a lock, Apple Find My or GPS;
Bosch GPS/alarm need a ConnectModule and a Flow+ subscription and are not counted). Bikes without a stored spec table (the hand-collected Orbea rows) have no display or shifting data.
sort_by: weight | battery | torque | travel | year | brand | price. limit: up to 500 (default 50). |
get_bike | One bike by bike_id: every field, the notes explaining derived or missing values,
the brand page's own spec rows, each stored price with its source and date, features (shifting, wireless dropper, charger amps)
and display (what its display unit is and does, with the source URL). |
list_options | The values available for each filter, with bike counts, and the database's ranges (weight, battery, torque, price). |
scrape_status | When each brand was last read and how that run went. |
JSON API
| Endpoint | Returns |
|---|---|
GET /api/bikes | Every bike (summary columns). |
GET /api/bikes/{id} | One bike: all columns, field notes, raw page specs, prices, features and its display unit. |
GET /api/runs | The 30 most recent scrape runs. |
GET /api/status | Latest scrape per brand, and brands that come from hand-collected data. |
Interactive reference with response schemas: /docs (OpenAPI).