mcp_canadiantire
# mcp-canadiantire
[](./LICENSE.md)
[](https://nodejs.org)
[](https://modelcontextprotocol.io)
A [Model Context Protocol](https://modelcontextprotocol.io) server for **Canadian Tire**
(canadiantire.ca). It exposes the store's catalogue — product search, product detail,
store-level stock and a store finder — to MCP clients such as Claude and Cursor.
> **Unofficial.** This project is not affiliated with, endorsed by, or sponsored by
> Canadian Tire Corporation. It reads publicly available endpoints of canadiantire.ca
> for personal, noncommercial use. Respect Canadian Tire's terms of service and use
> responsibly.
## Tools
| Tool | Description |
|------|-------------|
| `ct_search` | Product search / listings by keyword. Returns products (SKU, name, brand, model, price + sale price, rating, in-store & online stock, URL, image), total count, facets and sort options. Follows category redirects automatically. |
| `ct_product` | Full product card by SKU: name, brand, model, description, features, images, rating/reviews, warranty, categories, specifications, and per-variant price + in-store stock with shelf/aisle location, online stock and fulfillment options. |
| `ct_store_availability` | In-store stock of a SKU across nearby stores, sorted in-stock first then nearest. Per store: name, address, phone, coordinates, distance, stock quantity, shelf/aisle location, pickup availability. Plus online stock. |
| `ct_stores` | Find stores nearest to a postal code or coordinates (distance-sorted with `distanceKm`). Per store: id, name, address, town, province, postal code, phone, coordinates, URL, services, fulfillment eligibility. |
### `ct_search`
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| `query` | string | — | Search keyword (required). |
| `store` | string | `676` | Store id for store-specific stock/pricing. |
| `lang` | `en` \| `fr` | `en` | Response language. |
| `sort` | `relevance` \| `price-asc` \| `price-desc` \| `newest` \| `rating` \| `bestseller` | `relevance` | Sort order. |
| `page` | int | `1` | 1-indexed page. |
| `pageSize` | int (1–100) | `24` | Results per page. |
### `ct_product`
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| `sku` | string | — | Product code (required), e.g. `0542448P` or `054-2448-6`. |
| `postalCode` | string | — | Postal code, e.g. `M5V 2T6`; uses the nearest store (overrides `storeId`). |
| `storeId` | string | `676` | Store id for store-specific price/stock/shelf. Override default with `CT_DEFAULT_STORE`. |
| `lang` | `en` \| `fr` | `en` | Response language. |
### `ct_store_availability`
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| `sku` | string | — | Product code (required). |
| `postalCode` | string | — | Postal code; nearby stores are taken around it (overrides `storeId`). |
| `storeId` | string | `676` | Reference store; nearby stores are taken around it. |
| `lang` | `en` \| `fr` | `en` | Response language. |
| `limit` | int (1–25) | `10` | Max number of stores to check. |
> **Default store:** product/availability tools default to store `676` (Edmonton Windermere, AB).
> Set `CT_DEFAULT_STORE`, or pass `storeId` / `postalCode`, to use your local store.
> Use `ct_stores` with a postal code to find the right store id.
### `ct_stores`
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| `postalCode` | string | — | Canadian postal code, e.g. `M5V 2T6`. Geocoded, then distance-sorted. |
| `latitude` / `longitude` | number | — | Alternative to `postalCode` for proximity. |
| `lang` | `en` \| `fr` | `en` | Response language. |
| `limit` | int (1–50) | `20` | Max stores to return. |
> The store API geosorts by coordinates only (no postal-code search), so a postal code is
> first geocoded via `api.zippopotam.us` (free, no key). `distanceKm` is returned by Canadian Tire.
## How it talks to Canadian Tire
`canadiantire.ca/api/*` is gated by an **Azure API Management subscription key** — the
`ocp-apim-subscription-key` header. With that key, requests succeed over a plain Node
`fetch`: no cookies, no Akamai `_abck`, and no TLS (JA3) fingerprinting to work around
(unlike some other retailers). So this server talks to the API directly with `fetch` —
no `curl` subprocess required.
The key is a public client key embedded in the site's JavaScript. If Canadian Tire rotates
it (calls start returning `401`), set a new one via the `CT_SUBSCRIPTION_KEY` env var —
re-scrape it from the `ocp-apim-subscription-key` request header of any search XHR on
canadiantire.ca.
## Build & run
```bash
npm install
npm run build
npm start # runs the MCP server on stdio
```
Quick smoke test of the search layer:
```bash
node --input-type=module -e 'import {search} from "./dist/search.js"; console.log(await search({query:"drill", pageSize:3}))'
```
Inspect with the MCP Inspector:
```bash
npm run inspect
```
## Connecting a client
Add to your MCP client config (e.g. Claude Desktop `claude_desktop_config.json`),
pointing at the built entrypoint:
```json
{
"mcpServers": {
"canadiantire": {
"command": "node",
"args": ["/absolute/path/to/mcp_canadiantire/dist/index.js"],
"env": {
"CT_DEFAULT_STORE": "676",
"CT_SUBSCRIPTION_KEY": "<only if the bundled key stops working>"
}
}
}
}
```
Both env vars are **optional**:
- `CT_DEFAULT_STORE` — default store id for the product/availability tools (defaults to
`676`, Edmonton Windermere, AB).
- `CT_SUBSCRIPTION_KEY` — overrides the bundled Azure APIM subscription key. See below.
### About the subscription key
Every `canadiantire.ca/api/*` request needs an `ocp-apim-subscription-key` header. This is
a **public client key** embedded in the site's own JavaScript — not a personal secret — and
this server ships with a working value baked in, so it works out of the box with no setup.
If Canadian Tire rotates the key (requests suddenly start returning **HTTP 401**), grab a
fresh one and pass it via `CT_SUBSCRIPTION_KEY`:
1. Open <https://www.canadiantire.ca> and run any search.
2. In the browser dev tools **Network** tab, find a request to `/api/v1/search/v2/search`.
3. Copy the value of its `ocp-apim-subscription-key` request header.
4. Set it as `CT_SUBSCRIPTION_KEY` (env var or the client config `env` block above).
## Project layout
```
src/
index.ts MCP server + tool registration (stdio)
ct.ts fetch transport for canadiantire.ca (APIM key + headers)
search.ts ct_search implementation + response mapping
product.ts ct_product + ct_store_availability (productFamily + PriceAvailability APIs)
stores.ts ct_stores (store finder + postal-code geocode → nearest)
```
## License
[PolyForm Noncommercial License 1.0.0](./LICENSE.md) — free to use, modify and share
for **noncommercial** purposes. Commercial use requires a separate license from the author.
TDQS
Scored across 4 tools
Each tool has a clearly distinct purpose: ct_product fetches full product details for a specific SKU at a store, ct_search queries products by keyword, ct_store_availability checks stock across stores for a SKU, and ct_stores finds nearby stores. No overlap in functionality.
All tool names follow a consistent 'ct_' prefix followed by a descriptive noun or noun phrase: product, search, store_availability, stores. The naming pattern is uniform and predictable.
With 4 tools, the server is well-scoped for a retail product and store information domain. Each tool serves a necessary function without redundancy or gap, covering search, details, stock, and store localization.
The tool set provides a complete surface for typical Canadian Tire interactions: product search, full product card retrieval, store stock lookup, and store finder. No obvious missing operations like category browsing or list management, which are likely out of scope.