Documentation

E-MTB database for Texas - MCP server and JSON API

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

ToolWhat it does
search_bikesFind 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_bikeOne 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_optionsThe values available for each filter, with bike counts, and the database's ranges (weight, battery, torque, price).
scrape_statusWhen each brand was last read and how that run went.

JSON API

EndpointReturns
GET /api/bikesEvery bike (summary columns).
GET /api/bikes/{id}One bike: all columns, field notes, raw page specs, prices, features and its display unit.
GET /api/runsThe 30 most recent scrape runs.
GET /api/statusLatest scrape per brand, and brands that come from hand-collected data.

Interactive reference with response schemas: /docs (OpenAPI).