ikea-mcp 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -8
- package/dist/data/stores.js +24 -2
- package/dist/http.js +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,9 +17,9 @@ Read-only MCP server for IKEA product search and in-store stock lookup.
|
|
|
17
17
|
|
|
18
18
|
## MVP limitations
|
|
19
19
|
- Uses unofficial public IKEA APIs — no SLA, may break without notice
|
|
20
|
-
- ~
|
|
20
|
+
- ~65 confirmed US and Canada stores in catalog (`src/data/stores.ts`); some IDs remain unverified
|
|
21
21
|
- Cash-and-carry availability only — click-and-collect and home delivery not exposed
|
|
22
|
-
- HTTP transport
|
|
22
|
+
- HTTP transport is open by default — set `API_KEY` env var to require `x-api-key` header on `/mcp`
|
|
23
23
|
- Read-only — no cart, order, or account operations
|
|
24
24
|
|
|
25
25
|
## Tools
|
|
@@ -190,7 +190,51 @@ Find stores with the highest in-stock quantity for an item. Queries stores in pa
|
|
|
190
190
|
]
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
-
Returns `[]` if no store has the item in stock. "All known stores" means the ~
|
|
193
|
+
Returns `[]` if no store has the item in stock. "All known stores" means the ~65 US and Canada entries in `src/data/stores.ts`.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Example workflows
|
|
198
|
+
|
|
199
|
+
### 1. Search → inspect → check one store
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
1. search_products { "query": "BILLY bookcase" }
|
|
203
|
+
→ pick itemNo from results, e.g. "20522046"
|
|
204
|
+
|
|
205
|
+
2. get_product_details { "itemNo": "20522046" }
|
|
206
|
+
→ confirms name, price, dimensions before checking stock
|
|
207
|
+
|
|
208
|
+
3. check_store_stock { "itemNo": "20522046", "storeId": "399" }
|
|
209
|
+
→ { "availableForCashCarry": true, "quantity": 95, "messageType": "HIGH_IN_STOCK" }
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### 2. Shopping list at one store
|
|
213
|
+
|
|
214
|
+
Check whether several items are available in a single trip:
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"tool": "check_multi_item_stock",
|
|
219
|
+
"storeId": "399",
|
|
220
|
+
"itemNos": ["20522046", "40477340", "89268919"]
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Returns one entry per item in the same order — items not stocked appear with `availableForCashCarry: false` and a 404 error.
|
|
225
|
+
|
|
226
|
+
### 3. Best store from a mixed US + Canada subset
|
|
227
|
+
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
"tool": "find_best_store_for_item",
|
|
231
|
+
"itemNo": "20522046",
|
|
232
|
+
"storeIds": ["399", "039", "216", "149", "026"],
|
|
233
|
+
"maxResults": 3
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Returns the top 3 stores by in-stock quantity across the mixed US/Canada subset. Omit `storeIds` to search all ~65 known stores.
|
|
194
238
|
|
|
195
239
|
---
|
|
196
240
|
|
|
@@ -244,7 +288,11 @@ Endpoints after deploy:
|
|
|
244
288
|
- `POST /mcp` — MCP Streamable HTTP (requires `Accept: application/json, text/event-stream`)
|
|
245
289
|
- `GET /health` — returns `{"status":"ok"}`
|
|
246
290
|
|
|
247
|
-
> **Security note:**
|
|
291
|
+
> **Security note:** Set `API_KEY` to protect the `/mcp` endpoint. Requests without a matching `x-api-key` header return 401. `/health` is always open. The server is read-only — no cart, order, or account operations are possible.
|
|
292
|
+
>
|
|
293
|
+
> ```bash
|
|
294
|
+
> API_KEY=your-secret node dist/http.js
|
|
295
|
+
> ```
|
|
248
296
|
|
|
249
297
|
## Connecting a local MCP client (stdio)
|
|
250
298
|
|
|
@@ -302,12 +350,12 @@ The `Accept: application/json, text/event-stream` header is required by the MCP
|
|
|
302
350
|
|
|
303
351
|
## Store IDs
|
|
304
352
|
|
|
305
|
-
Store metadata (ID → city label) lives in `src/data/stores.ts`. ~50 US stores
|
|
353
|
+
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).
|
|
306
354
|
|
|
307
355
|
Confirmed compatible `storeId` formats:
|
|
308
|
-
- Standard 3-digit: `"399"` (Burbank, CA)
|
|
309
|
-
- Leading-zero 3-digit: `"026"` (Canton, MI), `"
|
|
310
|
-
- 4-digit: `"921"` (Brooklyn, NY), `"1129"` (Syracuse, NY)
|
|
356
|
+
- Standard 3-digit: `"399"` (Burbank, CA, US), `"216"` (Calgary, AB, CA)
|
|
357
|
+
- Leading-zero 3-digit: `"026"` (Canton, MI, US), `"039"` (Montreal, QC, CA)
|
|
358
|
+
- 4-digit: `"921"` (Brooklyn, NY, US), `"1129"` (Syracuse, NY, US)
|
|
311
359
|
|
|
312
360
|
Some stores remain unconfirmed (no store page ID found or not yet probed). A few valid-looking IDs from the stock API have no known city mapping — see TODO comments in `stores.ts`.
|
|
313
361
|
|
package/dist/data/stores.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
// US store labels confirmed from ikea.com/us/en/stores/<slug>/ pages (store_id= survey param).
|
|
2
|
+
// CA store labels confirmed from ikea.com/ca/en/stores/<slug>/ pages (storeNo= embedded JSON).
|
|
2
3
|
// IDs match the sto/{storeId} path used by the IKEA stock API.
|
|
3
4
|
// 4-digit IDs (921, 1099, 1129) are newer-format stores — confirmed working with stock API.
|
|
4
5
|
// 3136 (San Francisco) returns 405 from stock API — excluded.
|
|
5
|
-
//
|
|
6
|
-
//
|
|
6
|
+
// 698 (CA — unknown city) returns 422 from stock API — excluded.
|
|
7
|
+
// 659 (Toronto Downtown) returns 200 but null quantity — included conservatively.
|
|
8
|
+
// TODO: huntsville, arcadia, mcallen-pharr, san-marcos (US) — IDs not found on store pages.
|
|
9
|
+
// TODO: 448, 612, 634, 652, 661, 663, 692, 693 — valid API IDs, city/country unknown.
|
|
7
10
|
export const STORE_LABELS = {
|
|
11
|
+
// ── United States ────────────────────────────────────────────────────────────
|
|
8
12
|
"026": "026 (Canton, MI)",
|
|
9
13
|
"027": "027 (Round Rock, TX)",
|
|
10
14
|
"028": "028 (Portland, OR)",
|
|
@@ -57,6 +61,24 @@ export const STORE_LABELS = {
|
|
|
57
61
|
"921": "921 (Brooklyn, NY)",
|
|
58
62
|
"1099": "1099 (University Park, TX)",
|
|
59
63
|
"1129": "1129 (Syracuse, NY)",
|
|
64
|
+
// ── Canada ───────────────────────────────────────────────────────────────────
|
|
65
|
+
// Confirmed from ikea.com/ca/en/stores/<slug>/ pages (storeNo= embedded JSON).
|
|
66
|
+
// All probed against stock API with item 20522046 — all return 200.
|
|
67
|
+
"003": "003 (Richmond, BC, CA)",
|
|
68
|
+
"004": "004 (Ottawa, ON, CA)",
|
|
69
|
+
"039": "039 (Montreal, QC, CA)",
|
|
70
|
+
"040": "040 (Burlington, ON, CA)",
|
|
71
|
+
"149": "149 (North York, ON, CA)",
|
|
72
|
+
"216": "216 (Calgary, AB, CA)",
|
|
73
|
+
"249": "249 (Winnipeg, MB, CA)",
|
|
74
|
+
"256": "256 (Etobicoke, ON, CA)",
|
|
75
|
+
"313": "313 (Coquitlam, BC, CA)",
|
|
76
|
+
"349": "349 (Edmonton, AB, CA)",
|
|
77
|
+
"372": "372 (Vaughan, ON, CA)",
|
|
78
|
+
"414": "414 (Boucherville, QC, CA)",
|
|
79
|
+
"529": "529 (Halifax, NS, CA)",
|
|
80
|
+
"559": "559 (Quebec City, QC, CA)",
|
|
81
|
+
"659": "659 (Toronto Downtown, ON, CA)",
|
|
60
82
|
};
|
|
61
83
|
export function storeLabel(storeId) {
|
|
62
84
|
return STORE_LABELS[storeId];
|
package/dist/http.js
CHANGED
|
@@ -3,6 +3,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
3
3
|
import { createServer } from "./server.js";
|
|
4
4
|
const PORT = Number(process.env.PORT ?? 3000);
|
|
5
5
|
const BODY_LIMIT = 1024 * 1024; // 1 MB
|
|
6
|
+
const API_KEY = process.env.API_KEY ?? "";
|
|
6
7
|
function readBody(req) {
|
|
7
8
|
return new Promise((resolve, reject) => {
|
|
8
9
|
let raw = "";
|
|
@@ -40,6 +41,10 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
40
41
|
sendJson(res, 200, { status: "ok" });
|
|
41
42
|
return;
|
|
42
43
|
}
|
|
44
|
+
if (API_KEY && req.headers["x-api-key"] !== API_KEY) {
|
|
45
|
+
sendJson(res, 401, { error: "unauthorized" });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
43
48
|
if (method === "POST" && url === "/mcp") {
|
|
44
49
|
const body = await readBody(req);
|
|
45
50
|
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
@@ -60,4 +65,5 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
60
65
|
httpServer.listen(PORT, () => {
|
|
61
66
|
console.error(`ikea-mcp HTTP listening on http://localhost:${PORT}/mcp`);
|
|
62
67
|
console.error(`health: http://localhost:${PORT}/health`);
|
|
68
|
+
console.error(`auth: ${API_KEY ? "API_KEY required (x-api-key header)" : "open (set API_KEY to enable)"}`);
|
|
63
69
|
});
|
package/package.json
CHANGED