ALLR MCP connector
ALLR exposes its motorcycle-gear price data to AI assistants over the Model Context Protocol. One HTTPS endpoint, no authentication, read-only. An assistant connected to it can search the catalog, pull the full per-retailer landed-cost table for a product in a rider’s country, list current price drops, and recommend a size from body measurements against the manufacturer’s own chart.
| Endpoint | https://allr.io/mcp |
| Connection type | MCP |
| Transport | Streamable HTTP, stateless — POST a JSON-RPC 2.0 message, get an application/json response. No SSE stream, no session id. |
| Protocol versions | 2025-06-18, 2025-03-26 |
| Authentication | None. Public and read-only. Send no credentials; none are accepted, stored or required. |
| Rate limit | 300 JSON-RPC requests per minute per source IP address, counted across the whole ALLR fleet. A batch costs one per request it carries. |
| CORS | Open. Access-Control-Allow-Origin: *, no credentials — a browser-hosted client can call it directly. |
| Tools | 5, all read-only (search_gear, get_product_offers, get_deals, get_size_recommendation, list_categories_and_brands) |
| Support | allr.io/contact |
Connecting
Point any MCP client at https://allr.io/mcp as a remote/HTTP server with no auth. The server is stateless: every POST is self-contained, so there is no Mcp-Session-Id to carry and no ordering requirement beyond the protocol’s own initialize handshake.
- POST — the working method. One JSON-RPC request, or (under protocol version 2025-03-26, which includes a request that sends no MCP-Protocol-Version header) a batch array.
- OPTIONS — 204 with the CORS preflight headers. GET and DELETE — 405 with Allow: POST, OPTIONS. There is no server-initiated stream and no session to terminate.
- Notifications (a JSON-RPC message with no id, such as notifications/initialized) — 202 with an empty body.
- Methods — initialize, ping, tools/list, tools/call. Anything else returns JSON-RPC -32601.
- Batches — at most 10 messages per array. A longer batch is refused whole, with a single -32600 and a 400; no message in it runs. Each request in a batch costs its own rate-limit token (notifications are free), so a batch is metered at what it actually spends — see Rate limits.
- Errors — malformed JSON is -32700, a bad envelope -32600, an unknown tool or bad arguments -32602. A tool that ran and failed returns a normal result with isError: true, so the model can read the message and recover. An unexpected server-side failure returns a generic message plus an errorId — quote it to support; internal details are never put on the wire.
- Body limit — 256 KB. Larger requests get 413.
- Protocol version — every response carries an MCP-Protocol-Version header naming the version that exchange ran under: the negotiated version for initialize, otherwise your own request header, or 2025-03-26 when you send none (the spec’s default).
- CORS — every response carries Access-Control-Allow-Origin: *. No credentials are accepted or expected, so a browser-hosted client can call the endpoint directly; MCP-Protocol-Version, Retry-After and the X-RateLimit-* headers are exposed so cross-origin code can read them.
Batching
curl -s https://allr.io/mcp \
-H 'Content-Type: application/json' \
-H 'MCP-Protocol-Version: 2025-03-26' \
-d '[
{ "jsonrpc": "2.0", "id": 1, "method": "ping" },
{ "jsonrpc": "2.0", "method": "notifications/initialized" },
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }
]'Batching exists in protocol version 2025-03-26 and was removed in 2025-06-18: send MCP-Protocol-Version: 2025-06-18 and an array is refused with -32600. One message per POST is always accepted, and is what every current SDK client sends.
Handshake
curl -s https://allr.io/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": { "name": "my-assistant", "version": "1.0.0" }
}
}'{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": { "tools": { "listChanged": false } },
"serverInfo": { "name": "allr", "title": "ALLR — motorcycle gear price comparison", "version": "1.0.0" },
"instructions": "ALLR compares motorcycle gear …"
}
}The instructions string tells the model how to use the tools well — above all, to establish the rider’s country before quoting a price, because ALLR ranks by landed cost and the cheapest retailer genuinely changes between markets.
Calling a tool
curl -s https://allr.io/mcp \
-H 'Content-Type: application/json' \
-H 'MCP-Protocol-Version: 2025-06-18' \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "search_gear",
"arguments": { "query": "agv k6", "country": "CA", "limit": 2 }
}
}'{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [{ "type": "text", "text": "2 products matched \"agv k6\" — showing 2. …" }],
"structuredContent": {
"query": "agv k6",
"country": "CA",
"currency": "CAD",
"ranked_match_count": 2,
"results_capped": false,
"next_cursor": null,
"as_of": "2026-09-19T14:02:11.418Z",
"results": [
{
"id": "agv-k6-s-helmet",
"name": "AGV K6 S Helmet",
"brand": "AGV",
"category": "Full-Face Helmet",
"gender": "unisex",
"certifications": ["ECE 22.06", "DOT"],
"lowest_landed_price": 559.99,
"currency": "CAD",
"offer_count": 11,
"in_stock_offer_count": 9,
"ships_to_country": true,
"image_url": "https://…",
"url": "https://allr.io/product/agv-k6-s-helmet"
}
]
},
"isError": false
}
}Every tool returns both a machine-readable structuredContent object matching its declared outputSchema and a compact content[0].text summary, so a client that reads only one of the two still gets the whole answer.
Rate limits
300 JSON-RPC requests per minute per source IP address, counted across the whole ALLR fleet (not per server task), in a fixed one-minute window. The unit is the request, not the HTTP call: a batch of five tools/call messages costs five, and notifications cost nothing (every HTTP call costs at least one). The whole batch is priced before any of it runs, so a batch that would overrun your allowance is refused in full rather than half-executed.
Every response — not only a 429 — carries X-RateLimit-Limit and X-RateLimit-Remaining, so you can see the budget fall as you spend it. Over the limit, the endpoint returns 429 with Retry-After in seconds and this body:
HTTP/1.1 429 Too Many Requests
Retry-After: 37
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
{ "error": "Too many requests", "retryAfterMs": 37000 }Back off for the stated interval and retry; do not retry tighter than Retry-After.
The limit is deliberately set well above what a single conversation needs, because an assistant platform’s traffic arrives from a handful of shared egress addresses rather than from each user’s own IP. If you expect sustained volume above this from a fixed address range, get in touch and we will raise it for you rather than have you discover it as a 429.
A separate network-layer protection sits in front of the application and blocks any source address exceeding 2,000 requests in a 5-minute window. The application limit above is set below it on purpose, so a busy client receives an explicit 429 with a retry interval rather than an opaque block.
Tools
All 5 tools are read-only: they never write, never take a payment, never act on a rider’s behalf, and every one is annotated readOnlyHint: true and openWorldHint: false. The parameters below are generated from the same registry tools/list serves, so this page cannot drift from the endpoint.
search_gearSearch motorcycle gear
Search ALLR's motorcycle-gear catalog (helmets, jackets, suits, pants, boots, gloves, protection, riding shirts, base layers, communication, luggage, accessories) by free text, and get each product's lowest landed cost for a buyer country, how many retailers carry it, and its allr.io product-page URL. Use this to FIND a product; then call get_product_offers with the returned `id` for the per-retailer price table. The `lowest_landed_price` is the cheapest total including shipping, duty and VAT for `country` — not a sticker price — and is one number out of a full table, so do not present it as the final answer on its own.
| Parameter | Type | Required | Description |
|---|---|---|---|
| query | string | yes | Free-text search, e.g. 'agv k6', 'waterproof adventure jacket', 'alpinestars smx-6 boots'. Brand names, model names and model numbers all work. |
| country | string CA | US | EU | UK default "US" | no | Buyer country (ISO-2, plus 'EU' and 'UK' as market codes). Determines the currency and the landed cost: item price + shipping + import duty + VAT. ALLR computes landed cost for these four markets only. ASK the rider rather than guessing. |
| brand | string | no | Restrict to a brand. Use a `slug` or `name` from list_categories_and_brands. Applied as a brand-token filter on the matched products. |
| category | string | no | Restrict to a catalog category. Use a `slug` from list_categories_and_brands (e.g. 'helmets', 'jackets', 'boots'). |
| gender | string mens | womens | unisex | kids | no | Restrict to products cut for this rider group, using ALLR's own gender classifier. |
| certification | string | no | Restrict to products carrying this safety certification label, e.g. 'ECE 22.06', 'DOT', 'SNELL', 'CE AA'. Matched case-insensitively against the same certification labels the ALLR catalog filters on. A product whose certification ALLR cannot derive from its catalog row returns no `certifications` and is excluded by this filter — check the product page before telling a rider something is uncertified. |
| min_price | number | no | Lowest acceptable landed price in the country's currency. |
| max_price | number | no | Highest acceptable landed price in the country's currency. |
| limit | integer default 10 | no | Maximum products to return (1-25). |
| cursor | string | no | Opaque pagination cursor from a previous call's `next_cursor`. Omit for the first page. |
Returns (as both structuredContent and a compact content[0].text summary): query (string), country (string), currency (string), ranked_match_count (integer), results_capped (boolean), next_cursor (string), as_of (string), results (array<object>).
get_product_offersGet every retailer offer for a product
Get the full per-retailer offer table for ONE ALLR product in a buyer country: landed cost broken down into item price, shipping, import duty and tax, plus stock, condition, per-colorway size availability, whether the retailer ships to that country, and an attributed buy link. This is the authoritative answer to 'where is it cheapest' — search_gear only carries the single lowest number. Offers are sorted cheapest landed first; a retailer that does not ship to the country is still listed, flagged and sorted last. Availability is truth per (retailer x colorway x size): each offer carries `availability_by_colorway`, one row per colorway with ITS OWN sizes. There is no offer-level size list, because a size in stock in one colorway says nothing about another — quote a size only with its colorway.
| Parameter | Type | Required | Description |
|---|---|---|---|
| product_id | string | yes | ALLR product id from search_gear, or a full https://allr.io/product/<id> URL, or the bare slug. A merged/renamed id is followed to its canonical product automatically. |
| country | string CA | US | EU | UK default "US" | no | Buyer country (ISO-2, plus 'EU' and 'UK' as market codes). Determines the currency and the landed cost: item price + shipping + import duty + VAT. ALLR computes landed cost for these four markets only. ASK the rider rather than guessing. |
| colorway | string | no | Restrict to offers carrying this colorway. Matched against the product's real colorway NAMES the way the product page matches them: the normalized name first ('matt black' = 'Matt Black'), and only if nothing matches exactly, any colorway whose name contains all of those words as whole words ('black' matches 'Matt Black' AND 'Black/Red'). A value that matches no colorway on this product comes back as an error listing the valid names. Omit to see every colorway. |
| size | string | no | Restrict to offers that have this size IN STOCK in at least one colorway, e.g. 'L', 'XL', '44'. The kept offers carry `matched_colorways` — exactly the colorways that have it. Combined with `colorway`, the offer must have that size IN that colorway. An offer with no per-colorway breakdown is dropped by this filter rather than guessed at. |
| include_out_of_stock | boolean default true | no | Keep out-of-stock offers in the table (default true — ALLR never hides them). Set false to see only buyable offers. |
| limit | integer default 40 | no | Maximum offers to return (1-40). |
Returns (as both structuredContent and a compact content[0].text summary): product (object), country (string), currency (string), as_of (string), offer_count (integer), offers (array<object>).
get_dealsCurrent motorcycle gear price drops
List motorcycle gear currently discounted on ALLR, with the same filters the site's Hot Deals board uses. Two boards: the default MSRP board (price below manufacturer list) and mode='retailer' (only deals where the store cut its OWN advertised price, ranked by that store's percentage). Returns the discounted landed price for the buyer country, the percentage off, the retailer featuring it and the allr.io product URL.
| Parameter | Type | Required | Description |
|---|---|---|---|
| country | string CA | US | EU | UK default "US" | no | Buyer country (ISO-2, plus 'EU' and 'UK' as market codes). Determines the currency and the landed cost: item price + shipping + import duty + VAT. ALLR computes landed cost for these four markets only. ASK the rider rather than guessing. |
| category | string | no | Catalog category slug from list_categories_and_brands (e.g. 'helmets', 'jackets'). |
| subcategory | string | no | Helmet/riding-shirt subtype slug. Only meaningful together with `category`. |
| retailer | string | no | Retailer id — keeps only deals this retailer can fill, and features ITS price. |
| size | string | no | Normalized size label ('XL', '44') — keeps only deals whose featured offer has that size in stock. |
| min_discount_pct | integer 10 | 20 | 30 | 40 | no | Minimum percentage off. |
| mode | string msrp | retailer default "msrp" | no | 'msrp' (default) = below manufacturer list price. 'retailer' = the store cut its own advertised price. |
| limit | integer default 20 | no | Maximum deals to return (1-50). |
Returns (as both structuredContent and a compact content[0].text summary): country (string), currency (string), mode (string), total_matches (integer), results_capped (boolean), computed_at (string), as_of (string), deals (array<object>).
get_size_recommendationRecommend a size from body measurements
Recommend a size for a specific product (or a brand + category) from the rider's body measurements, using the manufacturer's own published size chart — the same charts and the same matcher the ALLR product page uses. Measurements are in CENTIMETRES: head circumference for helmets, chest for jackets/suits, hand circumference for gloves, foot length for boots. Returns the recommended label with its EU/US/UK equivalents, how snug it falls, and a link to the brand's official chart. When no chart exists for the brand+category, or the needed measurement is missing, it says so rather than guessing.
| Parameter | Type | Required | Description |
|---|---|---|---|
| product_id | string | no | ALLR product id or /product/ URL. Supplies the brand, category and model so the right chart variant is picked. Provide this OR both `brand` and `category`. |
| brand | string | no | Brand name, e.g. 'AGV', 'Alpinestars'. Required when `product_id` is omitted. |
| category | string | no | Product category, e.g. 'Full-Face Helmet', 'Leather Jacket', 'Boots', 'Gloves'. Required when `product_id` is omitted. |
| head_cm | number | no | Head circumference in cm (helmets). |
| chest_cm | number | no | Chest circumference in cm (jackets, suits). |
| hand_cm | number | no | Hand circumference in cm (gloves). |
| foot_cm | number | no | Foot length in cm (boots). |
| known_size | string | no | A size the rider already wears in this brand+category ('L', 'EU 44'), used when no measurement is given. |
Returns (as both structuredContent and a compact content[0].text summary): brand (string), category (string), product_id (string), recommended (object), needs (string), as_of (string).
list_categories_and_brandsList ALLR categories and brands
List the catalog categories and the brands ALLR tracks, with the exact slugs the `category` and `brand` filters on search_gear and get_deals expect. Call this first when you are about to filter, so a filter value is grounded in the catalog rather than guessed. Cheap and static — it reads bundled catalog metadata, not the live price data.
| Parameter | Type | Required | Description |
|---|---|---|---|
| include_brands | boolean default true | no | Include the brand list (a few hundred entries). Set false for categories only. |
| brand_query | string | no | Return only brands whose name or slug contains this text (case-insensitive). |
Returns (as both structuredContent and a compact content[0].text summary): categories (array<object>), brands (array<object>), brand_count (integer), countries (array<string>).
One offer, in full
A single entry from get_product_offers, so you can see the shape of the landed-cost breakdown:
{
"retailer": "FortNine",
"retailer_id": "fortnine",
"retailer_country": "CA",
"condition": null,
"in_stock": true,
"landed_total": 559.99,
"item_price": 559.99,
"shipping": 0,
"duty": 0,
"tax": 0,
"tax_name": null,
"free_shipping": true,
"domestic": true,
"ships_to_country": true,
"note": null,
"colorways": ["Matt Black", "Gloss White"],
"availability_by_colorway": [
{ "colorway": "Matt Black", "sizes_in_stock": ["S", "M"] },
{ "colorway": "Gloss White", "sizes_in_stock": ["L", "XL"] }
],
"availability_note": null,
"matched_colorways": null,
"sizes_listed": ["XS", "S", "M", "L", "XL", "2XL"],
"observed_at": "2026-09-19T09:41:02.000Z",
"buy_url": "https://allr.io/api/out?u=…&p=agv-k6-s-helmet&r=fortnine&src=mcp&c=…"
}What the numbers mean
- Prices are landed cost, not sticker price. landed_total is item price + shipping + import duty + applicable VAT, converted to the buyer country’s currency. That is the number ALLR ranks on. See Methodology for how each component is derived and what the method can and cannot prove.
- Country is required input, not a detail. Landed cost is computed for CA, US, EU, UK only, and the cheapest retailer differs between them. A tool called without a country defaults to US — ask the rider instead of relying on that.
- Availability is per (retailer × colorway × size). An offer carries availability_by_colorway — one row per colorway, each with its own sizes_in_stock. There is deliberately no offer-level size list: a size stocked in one colorway says nothing about another, so there is nothing to union by accident. An empty row means that colorway is out of stock at that retailer; null for the whole field means ALLR cannot attribute sizes to colorways for that offer at all, and availability_note says why. Never state a size without the colorway it belongs to.
- Colorways are asked for by name. The colorway filter matches the product’s real colorway names the way the product page does: the normalized name first, then — only if nothing matched exactly — any colorway containing all of those words as whole words. A value that matches nothing comes back as an error listing the valid names, so you can retry with one. Offers kept by a colorway or size filter carry matched_colorways, naming exactly which colorways satisfied it.
- A result count is a ranked count, not a catalog count. ALLR’s search ranks and returns at most 50 products per query, so search_gear reports ranked_match_count (what it ranked, after your filters) and results_capped. When the latter is true, more products match than one query can rank — narrow with brand or category rather than treating the number as a total. get_deals flags the same thing when the deals pool was itself truncated.
- At most one offer per (retailer, condition). A second row from the same store is a different condition — Open Box, Blemished, B-Stock, Refurbished or Discontinued — carried in condition. null means new.
- Retailers that cannot reach the rider are shown, not hidden. They come back with ships_to_country: false, a note naming the country, a null landed total, and they sort last. Present them as unavailable, never as a buyable price.
- Every product is visible in every region, and a product no retailer currently stocks stays visible with zero offers. Nothing is filtered out for being out of stock.
- Unknown is null, never estimated. A figure ALLR does not hold is omitted or null — including booleans: a search result whose per-country reachability rollup is missing returns ships_to_country: null rather than an assumed true. Where a shipping cost is ALLR’s own estimate rather than a retailer-quoted rate, the offer says so via shipping_estimated.
- Merged products resolve automatically. An id that has since been merged into another product is followed to its canonical product, and the response names the original in redirected_from.
Data freshness
Retailer prices and stock are re-scraped continuously; an individual offer carries observed_at, the moment ALLR last saw that listing. The deals pool is recomputed on a schedule and stamps its own computed_at. Every tool result carries as_of, the time the response was assembled.
Treat a price as an observation with a timestamp, not a standing fact. Quote it as “as of <timestamp>”, link the url so the rider can verify it, and expect it to move. ALLR does not guarantee that a retailer will honour a price it advertised.
Linking out
Use the buy_url on an offer verbatim. It is an allr.io redirect that logs the click and preserves affiliate attribution before forwarding to the retailer; a rewritten or unwrapped retailer link loses that. The redirect only forwards to retailer domains on ALLR’s allowlist. See the Affiliate Disclosure — ALLR does not reorder results by what a store pays; the lowest landed cost wins regardless.
Every buy_url from this endpoint carries a source marker — src=mcp and c=<client>, where client is a sanitised slug of your MCP client’s name (from clientInfo.name on initialize, otherwise your User-Agent). It is how ALLR can tell connector traffic from its own site’s, and it is the reason the link must be passed through unchanged: dropping or rewriting the query string makes the click indistinguishable from an unattributed one. The markers stay on the allr.io redirect and are never forwarded to the retailer — the outbound URL is rebuilt from the stored product URL alone.
What this endpoint logs
One structured line per JSON-RPC request, in ALLR’s own server logs: the method, the tool name, the argument key names plus three closed values (country, category, brand), the protocol version, the outcome, a row count, the duration, and the client-software slug described above. Free-text arguments are not recorded — a query or a body measurement never reaches a log line. These analytics lines carry no IP address, no cookie, no identifier of a person, and nothing that could distinguish one rider from another. (Request IPs are used for the rate-limit bucket and appear in ordinary load-balancer access logs, as they do for every request to any website.) See the Privacy Policy.
Attribution and terms
Use of this endpoint is governed by the ALLR Terms of Service and Privacy Policy. The endpoint is unauthenticated and receives no personal data: send none, and do not put rider identifiers in tool arguments.
When an assistant presents ALLR data to a person, attribute it to ALLR and link the product page returned in url. Redistributing bulk extracts of the catalog, or presenting ALLR’s prices as your own dataset, is not permitted. The connector is provided as-is, with no uptime guarantee.
Contact
Questions, a higher rate limit, a bug in a tool response, or a connector-directory submission: allr.io/contact. Machine-readable site index: allr.io/llms.txt.