ikea-mcp
Read-only MCP server for IKEA product search and in-store stock lookup.
README
ikea-mcp
Read-only MCP server for IKEA product search and in-store stock lookup.
Transports: stdio (Claude Desktop / MCP CLI) · Streamable HTTP (remote clients) License: MIT · No auth required to run locally
Capabilities
| Tool | What it does |
|---|---|
list_stores |
List known store IDs and labels, optionally filtered by country |
search_products |
Search IKEA products by keyword |
get_product_details |
Get details for a single product by item number |
check_store_stock |
Check cash-and-carry stock at one store |
check_multi_item_stock |
Check stock for multiple items at one store |
compare_store_stock |
Compare stock across explicit stores or a country catalog |
find_best_store_for_item |
Rank stores by in-stock quantity (optionally filter by country) |
check_cart_availability |
Check whether all items in a shopping list are available at one store |
find_best_store_for_cart |
Rank stores by cart fulfillment across multiple items |
MVP limitations
- Uses unofficial public IKEA APIs — no SLA, may break without notice
- Canada store coverage is complete (15 stores)
- US coverage is incomplete — 4 small-format stores have unknown API IDs (Queens, Alpharetta, Indianapolis, Arlington)
- San Francisco small-format store is intentionally excluded (known ID 3136 returns 405)
- No extra stores are included
- Cash-and-carry availability only — click-and-collect and home delivery not exposed
- HTTP transport is open by default — set
API_KEYenv var to requirex-api-keyheader on/mcp - Read-only — no cart, order, or account operations
Tools
search_products
Search IKEA products by keyword.
Input
| param | type | default | required |
|---|---|---|---|
query |
string | — | yes |
countryCode |
string | "US" |
no |
langCode |
string | "en" |
no |
size |
number | 10 |
no |
Output
{
"total": 97,
"items": [
{
"itemNo": "20522046",
"name": "BILLY",
"typeName": "Bookcase",
"salesPrice": { "amount": 69.99, "currencyCode": "USD" },
"pipUrl": "https://www.ikea.com/us/en/p/...",
"ratingValue": 4.8,
"ratingCount": 1234
}
]
}
get_product_details
Get details for a single IKEA product by item number.
Input
| param | type | default | required |
|---|---|---|---|
itemNo |
string | — | yes |
countryCode |
string | "US" |
no |
langCode |
string | "en" |
no |
Output
{
"itemNo": "20522046",
"name": "BILLY",
"typeName": "Bookcase",
"salesPrice": { "amount": 79, "currencyCode": "USD" },
"pipUrl": "https://www.ikea.com/us/en/p/billy-bookcase-white-20522046/",
"designText": "white",
"measureText": "31 1/2x11x79 1/2 \"",
"ratingValue": 4.6,
"ratingCount": 2620
}
shortDescriptionandmaterialsare not available from the underlying API.
check_store_stock
Check stock at a single IKEA store.
Input
| param | type | default | required |
|---|---|---|---|
itemNo |
string | — | yes |
storeId |
string | — | yes |
countryCode |
string | "US" |
no |
Output
{
"storeId": "399",
"availableForCashCarry": true,
"quantity": 110,
"messageType": "HIGH_IN_STOCK",
"errors": null
}
On error (e.g. item not carried):
{
"storeId": "026",
"availableForCashCarry": false,
"quantity": null,
"messageType": null,
"errors": [{ "code": 404, "message": "Not found", "meaning": "item not stocked at this store" }]
}
compare_store_stock
Compare stock for one item across multiple stores. Provide explicit storeIds, or use countryCode to expand to all catalog stores for that country. At least one of storeIds or countryCode is required.
Input
| param | type | default | required |
|---|---|---|---|
itemNo |
string | — | yes |
storeIds |
string[] (min 2) | — | one of storeIds/countryCode |
countryCode |
"US" | "CA" |
— | one of storeIds/countryCode |
sortBy |
"quantity" | "storeId" |
— | no |
storeIds takes precedence — if both are provided, countryCode only sets the IKEA API locale.
sortBy: "quantity" sorts descending, null quantities last, storeId as tie-breaker. sortBy: "storeId" sorts ascending. Omitting sortBy preserves input order.
Examples
{ "itemNo": "20522046", "storeIds": ["399", "026", "921"] }
{ "itemNo": "20522046", "countryCode": "CA" }
Output — array of the same shape as check_store_stock (one entry per store).
Detecting partial failures: rows with errors containing any code other than 404 indicate a store-level or API failure (e.g. 405 = invalid store ID). Rows with only 404 errors mean the item is simply not stocked at that store — this is expected, not a failure.
check_multi_item_stock
Check cash-and-carry stock for multiple items at a single store in one call.
Input
| param | type | default | required |
|---|---|---|---|
storeId |
string | — | yes |
itemNos |
string[] (min 1, max 20) | — | yes |
Output — array of per-item stock entries in the same order as itemNos:
[
{
"itemNo": "20522046",
"storeId": "399",
"storeLabel": "399 (Burbank, CA)",
"availableForCashCarry": true,
"quantity": 104,
"messageType": "HIGH_IN_STOCK",
"errors": []
}
]
Items not stocked at that store appear with availableForCashCarry: false, quantity: null, and a 404 error entry. An invalid storeId (405) returns that error on every entry.
find_best_store_for_item
Find stores with the highest in-stock quantity for an item. Queries stores in parallel, excludes invalid stores (405), out-of-stock stores (404), and stores with unknown quantity. Results sorted by quantity descending; ties broken by storeId lexicographically.
Input
| param | type | default | required |
|---|---|---|---|
itemNo |
string | — | yes |
storeIds |
string[] | all known stores | no |
maxResults |
number | 3 (max 50) |
no |
countryCode |
"US" | "CA" |
— | no |
minQuantity |
number (int ≥ 1) | — | no |
storeIds takes precedence. If only countryCode is given, searches all catalog stores for that country. If neither is given, searches all ~65 known stores. minQuantity excludes stores with quantity below the threshold.
Output — array of matching stores, up to maxResults:
[
{
"storeId": "399",
"storeLabel": "399 (Burbank, CA)",
"availableForCashCarry": true,
"quantity": 104,
"messageType": "HIGH_IN_STOCK"
}
]
Returns [] if no store has the item in stock. "All known stores" means the ~65 US and Canada entries in src/data/stores.ts.
Note on failures: stores that return a store-level error (405 invalid store ID) are silently excluded from results rather than appearing as rows. Use compare_store_stock with the same storeIds to inspect per-store errors directly.
check_cart_availability
Check whether all items in a shopping list are available in sufficient quantity at a single IKEA store.
Input
| param | type | default | required |
|---|---|---|---|
storeId |
string | — | yes |
items |
array of { itemNo, quantity } |
— | yes |
items[].itemNo |
string | — | yes |
items[].quantity |
number | 1 |
no |
Output
{
"storeId": "399",
"storeLabel": "399 (Burbank, CA)",
"allSufficient": true,
"items": [
{
"itemNo": "20522046",
"quantity": 2,
"inStock": 42,
"sufficient": true,
"eligibleForStockNotification": false,
"errors": []
}
]
}
allSufficient is true only when every item has sufficient: true. Items not stocked appear with inStock: null and a 404 error. An invalid storeId (405) propagates to all items.
find_best_store_for_cart
Find the best store to buy multiple items in one trip. Ranks stores by how many cart items are available in sufficient quantity, then by total in-stock sum. Optionally filter by countryCode or provide explicit storeIds.
Input
| param | type | default | required |
|---|---|---|---|
items |
array of { itemNo, quantity } |
— | yes |
items[].itemNo |
string | — | yes |
items[].quantity |
number | 1 |
no |
storeIds |
string[] | — | no |
countryCode |
"US" | "CA" |
— | no |
maxResults |
number | 3 (max 50) |
no |
storeIds takes precedence. If only countryCode is given, searches all catalog stores for that country. If neither is given, searches all ~65 known stores.
Output — array of stores ranked by cart fulfillment, up to maxResults:
[
{
"storeId": "399",
"storeLabel": "399 (Burbank, CA)",
"allSufficient": true,
"fulfilledCount": 3,
"totalCount": 3,
"items": [
{ "itemNo": "20522046", "quantity": 2, "inStock": 42, "sufficient": true },
{ "itemNo": "40477340", "quantity": 1, "inStock": 5, "sufficient": true },
{ "itemNo": "89268919", "quantity": 1, "inStock": 12, "sufficient": true }
]
}
]
fulfilledCount = number of items with sufficient: true. Sorting: fulfilledCount desc → total stock desc → storeId asc. Stores with invalid IDs (405) are excluded.
Example workflows
1. Search → inspect → check one store
1. search_products { "query": "BILLY bookcase" }
→ pick itemNo from results, e.g. "20522046"
2. get_product_details { "itemNo": "20522046" }
→ confirms name, price, dimensions before checking stock
3. check_store_stock { "itemNo": "20522046", "storeId": "399" }
→ { "availableForCashCarry": true, "quantity": 95, "messageType": "HIGH_IN_STOCK" }
2. Shopping list at one store
Check whether several items are available in a single trip:
{
"tool": "check_multi_item_stock",
"storeId": "399",
"itemNos": ["20522046", "40477340", "89268919"]
}
Returns one entry per item in the same order — items not stocked appear with availableForCashCarry: false and a 404 error.
3. Best store from a mixed US + Canada subset
{
"tool": "find_best_store_for_item",
"itemNo": "20522046",
"storeIds": ["399", "039", "216", "149", "026"],
"maxResults": 3
}
Returns the top 3 stores by in-stock quantity across the mixed US/Canada subset. Omit storeIds to search all ~65 known stores.
4. Best store for a shopping list
Find which store can fulfill the most items from a multi-item cart:
{
"tool": "find_best_store_for_cart",
"items": [
{ "itemNo": "20522046", "quantity": 2 },
{ "itemNo": "40477340", "quantity": 1 },
{ "itemNo": "89268919", "quantity": 1 }
],
"countryCode": "CA",
"maxResults": 3
}
Returns the top 3 Canada stores ranked by how many items they can fully supply. Use check_cart_availability to then verify exact quantities at the chosen store.
Build and test
npm install
npm run build # tsc → dist/
npm run typecheck # type-check without emit
npm test # unit tests
node smoke.mjs # end-to-end stdio smoke test
smoke.mjs exercises all 4 tools against the live IKEA API and prints pass/fail lines to stdout.
Transports
stdio (default — for Claude Desktop / MCP CLI):
npx ikea-mcp # after npm install (uses bin entry)
node dist/index.js # after local build
npm run dev # dev (tsx, no build needed)
Streamable HTTP (for remote / network clients):
node dist/http.js # listens on http://localhost:3000/mcp
PORT=8080 node dist/http.js
# or during dev:
npm run dev:http
Requests must include Accept: application/json, text/event-stream. Stateless — no session management.
Deploy (HTTP transport)
Tested target: Railway (also works on Render, Heroku, or any Procfile-aware host).
# 1. build
npm install && npm run build
# 2. run (Procfile: web: node dist/http.js)
# PORT is set automatically by the host
node dist/http.js
The Procfile in the repo root declares web: node dist/http.js. PORT is read from the environment (default 3000). No other env vars required.
Endpoints after deploy:
POST /mcp— MCP Streamable HTTP (requiresAccept: application/json, text/event-stream)GET /health— returns{"status":"ok"}
Security note: Set
API_KEYto protect the/mcpendpoint. Requests without a matchingx-api-keyheader return 401./healthis always open. The server is read-only — no cart, order, or account operations are possible.API_KEY=your-secret node dist/http.js
Connecting a local MCP client (stdio)
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"ikea-mcp": {
"command": "npx",
"args": ["-y", "ikea-mcp"]
}
}
}
Connecting a remote MCP client (HTTP)
Point your MCP client at https://<your-host>/mcp.
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"ikea-mcp": {
"type": "http",
"url": "https://<your-host>/mcp"
}
}
}
.mcp.json (project-local, Claude Code):
{
"mcpServers": {
"ikea-mcp": {
"type": "http",
"url": "https://<your-host>/mcp"
}
}
}
Manual / curl (for debugging):
curl -X POST https://<your-host>/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
The Accept: application/json, text/event-stream header is required by the MCP SDK — requests without it will be rejected with a -32000 error.
Store IDs
Store metadata (ID → city label) lives in src/data/stores.ts. ~50 US stores confirmed from ikea.com/us/en/stores/ pages; 15 Canada stores confirmed from ikea.com/ca/en/stores/ pages (all probed against the stock API).
Confirmed compatible storeId formats:
- Standard 3-digit:
"399"(Burbank, CA, US),"216"(Calgary, AB, CA) - Leading-zero 3-digit:
"026"(Canton, MI, US),"039"(Montreal, QC, CA) - 4-digit:
"921"(Brooklyn, NY, US),"1129"(Syracuse, NY, US)
An invalid or unsupported storeId returns a 405 error in the errors array.
Limitations
- Uses unofficial public IKEA APIs — no SLA, no auth required, may break without notice.
- Read-only: no cart, no order, no account operations.
- Country-wide fan-out (
countryCode: "US"≈ 52 stores,"CA"≈ 15) is capped at 10 concurrent requests and retries once on transient 5xx/network errors. - Click-and-collect and home-delivery availability are not exposed (cash-and-carry only).
sizeinsearch_productsis capped by IKEA's API (observed max ~24 per page;totalreflects the full catalogue count).- US and Canada only — no other countries supported.
Item numbers
itemNo fields accept several formats — all are normalised to 8 digits internally:
| Input | Normalised |
|---|---|
"20522046" |
"20522046" |
"522132" |
"00522132" |
"005.221.32" |
"00522132" |
"5-221-32" |
"00522132" |
6- and 7-digit inputs are left-padded to 8 digits. 8- and 9-digit inputs are kept as-is. Values outside 6–9 digits after stripping are rejected.
Supported countries
| Country | Code | Store count |
|---|---|---|
| United States | US |
~52 |
| Canada | CA |
~15 |
Use list_stores to get the current catalog. Some store IDs in the catalog are unverified — they are listed but may return 405 from the stock API.
Rate limits & reliability
- Fan-out requests (country-wide
compare_store_stock/find_best_store_for_item) are capped at 10 concurrent outbound requests. fetchJsonretries once after 500 ms on 5xx, 429, or network errors. 404 and 405 are not retried (they are semantic responses, not transient failures).Retry-Afterheader is respected for 429 responses.- Do not use in high-frequency loops — the upstream IKEA API has no published rate limit but will block repeated bursts.
Troubleshooting
| Symptom | Likely cause |
|---|---|
405 in errors |
Invalid storeId — use list_stores to find valid IDs |
404 in errors |
Item not stocked at that store |
Empty find_best_store_for_item result |
No store has the item in stock, or minQuantity is too high |
Slow countryCode query |
Normal — fan-out to all country stores (capped at 10 concurrent) |
itemNo validation error |
Input must resolve to 6–9 digits; see Item numbers above |
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。