Skip to main content
Glama
mcp_canadiantire

mcp_canadiantire

by sstepanovvl
README.md
# mcp-canadiantire

[![License: PolyForm Noncommercial 1.0.0](https://img.shields.io/badge/license-PolyForm--Noncommercial--1.0.0-blue.svg)](./LICENSE.md)
[![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
[![MCP](https://img.shields.io/badge/Model%20Context%20Protocol-server-orange.svg)](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

A4.4/5.0

Scored across 4 tools

Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count5/5

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.

Completeness5/5

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.