ikea-mcp 1.6.2 → 1.10.0
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/CHANGELOG.md +72 -0
- package/README.md +662 -526
- package/dist/data/countries.js +16 -0
- package/dist/data/geo.js +26 -0
- package/dist/data/store-coordinates.js +88 -0
- package/dist/data/stores.js +115 -86
- package/dist/schemas/index.js +51 -13
- package/dist/server.js +53 -16
- package/dist/services/ikea.js +4 -0
- package/dist/tools/check_cart_availability.js +7 -4
- package/dist/tools/check_multi_item_stock.js +2 -1
- package/dist/tools/check_store_stock.js +2 -2
- package/dist/tools/compare_store_stock.js +3 -2
- package/dist/tools/find_best_store_for_cart.js +19 -5
- package/dist/tools/find_best_store_for_item.js +19 -5
- package/dist/tools/find_nearby_stores.js +29 -0
- package/dist/tools/get_product_details.js +2 -2
- package/dist/tools/list_stores.js +11 -16
- package/dist/tools/search_products.js +2 -2
- package/dist/tools/targets.js +16 -0
- package/dist/utils/errors.js +27 -0
- package/dist/utils/http.js +6 -1
- package/package.json +59 -53
|
@@ -3,7 +3,7 @@ import { getMultiItemStock, projectMultiItemStock } from "../services/ikea.js";
|
|
|
3
3
|
import { storeLabel } from "../data/stores.js";
|
|
4
4
|
export const checkMultiItemStockTool = {
|
|
5
5
|
name: "check_multi_item_stock",
|
|
6
|
-
description: "Check cash-and-carry stock for multiple items at a single IKEA store.",
|
|
6
|
+
description: "Check cash-and-carry stock for multiple items at a single IKEA store. inRange: false on a row means the item is not sold at that store or in that country's market; null means the upstream response carried no range information.",
|
|
7
7
|
inputSchema: {
|
|
8
8
|
type: "object",
|
|
9
9
|
properties: {
|
|
@@ -20,6 +20,7 @@ export const checkMultiItemStockTool = {
|
|
|
20
20
|
storeId: input.storeId,
|
|
21
21
|
storeLabel: storeLabel(input.storeId) ?? input.storeId,
|
|
22
22
|
availableForCashCarry: row.availableForCashCarry,
|
|
23
|
+
inRange: row.inRange,
|
|
23
24
|
quantity: row.quantity,
|
|
24
25
|
messageType: row.messageType,
|
|
25
26
|
eligibleForStockNotification: row.eligibleForStockNotification,
|
|
@@ -3,13 +3,13 @@ import { getStoreStock, projectStock } from "../services/ikea.js";
|
|
|
3
3
|
import { storeLabel } from "../data/stores.js";
|
|
4
4
|
export const checkStoreStockTool = {
|
|
5
5
|
name: "check_store_stock",
|
|
6
|
-
description: "Check stock availability for an item at a specific IKEA store.",
|
|
6
|
+
description: "Check cash-and-carry stock availability for an item at a specific IKEA store. inRange: false means the item is not sold at that store or in that country's market; null means the upstream response carried no range information.",
|
|
7
7
|
inputSchema: {
|
|
8
8
|
type: "object",
|
|
9
9
|
properties: {
|
|
10
10
|
itemNo: { type: "string" },
|
|
11
11
|
storeId: { type: "string" },
|
|
12
|
-
countryCode: { type: "string", default: "
|
|
12
|
+
countryCode: { type: "string", default: "US" },
|
|
13
13
|
},
|
|
14
14
|
required: ["itemNo", "storeId"],
|
|
15
15
|
},
|
|
@@ -2,15 +2,16 @@ import { CompareStoreStockInput } from "../schemas/index.js";
|
|
|
2
2
|
import { getStoreStock, projectStock } from "../services/ikea.js";
|
|
3
3
|
import { storeLabel, storeIdsByCountry } from "../data/stores.js";
|
|
4
4
|
import { pMap } from "../utils/http.js";
|
|
5
|
+
import { COUNTRY_CODES } from "../data/countries.js";
|
|
5
6
|
export const compareStoreStockTool = {
|
|
6
7
|
name: "compare_store_stock",
|
|
7
|
-
description: "Compare stock
|
|
8
|
+
description: "Compare cash-and-carry stock for an item across multiple IKEA stores. Provide explicit storeIds, or use countryCode ('US', 'CA' or 'NO') to compare all catalog stores for that country. inRange: false on a row means the item is not sold at that store or in that country's market; null means the upstream response carried no range information.",
|
|
8
9
|
inputSchema: {
|
|
9
10
|
type: "object",
|
|
10
11
|
properties: {
|
|
11
12
|
itemNo: { type: "string" },
|
|
12
13
|
storeIds: { type: "array", items: { type: "string" }, minItems: 2 },
|
|
13
|
-
countryCode: { type: "string", enum: [
|
|
14
|
+
countryCode: { type: "string", enum: [...COUNTRY_CODES] },
|
|
14
15
|
sortBy: { type: "string", enum: ["quantity", "storeId"] },
|
|
15
16
|
},
|
|
16
17
|
required: ["itemNo"],
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { FindBestStoreForCartInput } from "../schemas/index.js";
|
|
2
2
|
import { getMultiItemStock, projectMultiItemStock } from "../services/ikea.js";
|
|
3
|
-
import {
|
|
3
|
+
import { storeLabel } from "../data/stores.js";
|
|
4
|
+
import { resolveTargets } from "./targets.js";
|
|
4
5
|
import { pMap } from "../utils/http.js";
|
|
6
|
+
import { COUNTRY_CODES } from "../data/countries.js";
|
|
5
7
|
export const findBestStoreForCartTool = {
|
|
6
8
|
name: "find_best_store_for_cart",
|
|
7
|
-
description: "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 ('US' or '
|
|
9
|
+
description: "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 distance when `near` is given, then by total in-stock sum. Optionally filter by countryCode ('US', 'CA' or 'NO'). Explicit storeIds take precedence over countryCode.",
|
|
8
10
|
inputSchema: {
|
|
9
11
|
type: "object",
|
|
10
12
|
properties: {
|
|
@@ -22,15 +24,22 @@ export const findBestStoreForCartTool = {
|
|
|
22
24
|
maxItems: 20,
|
|
23
25
|
},
|
|
24
26
|
storeIds: { type: "array", items: { type: "string" } },
|
|
25
|
-
countryCode: { type: "string", enum: [
|
|
27
|
+
countryCode: { type: "string", enum: [...COUNTRY_CODES] },
|
|
28
|
+
near: {
|
|
29
|
+
type: "object",
|
|
30
|
+
description: "Search only the stores closest to this point (the user's city or address). Use this instead of countryCode when you know where the user is.",
|
|
31
|
+
properties: { latitude: { type: "number" }, longitude: { type: "number" } },
|
|
32
|
+
required: ["latitude", "longitude"],
|
|
33
|
+
},
|
|
34
|
+
nearestStores: { type: "number", default: 5, description: "With near: how many of the closest stores to search (1–20)." },
|
|
35
|
+
maxDistanceKm: { type: "number", description: "Leave out stores farther than this." },
|
|
26
36
|
maxResults: { type: "number", default: 3 },
|
|
27
37
|
},
|
|
28
38
|
required: ["items"],
|
|
29
39
|
},
|
|
30
40
|
async handler(rawInput) {
|
|
31
41
|
const input = FindBestStoreForCartInput.parse(rawInput);
|
|
32
|
-
const targetIds = input
|
|
33
|
-
?? (input.countryCode ? storeIdsByCountry(input.countryCode) : Object.keys(STORE_LABELS));
|
|
42
|
+
const { storeIds: targetIds, distances } = resolveTargets(input);
|
|
34
43
|
const itemNos = input.items.map((i) => i.itemNo);
|
|
35
44
|
const storeResults = await pMap(targetIds, async (storeId) => {
|
|
36
45
|
const data = await getMultiItemStock(itemNos, storeId);
|
|
@@ -62,6 +71,10 @@ export const findBestStoreForCartTool = {
|
|
|
62
71
|
const fc = b.fulfilledCount - a.fulfilledCount;
|
|
63
72
|
if (fc !== 0)
|
|
64
73
|
return fc;
|
|
74
|
+
// With `near`, among stores that fill the cart equally well the closest comes first.
|
|
75
|
+
const closer = (distances.get(a.storeId) ?? 0) - (distances.get(b.storeId) ?? 0);
|
|
76
|
+
if (closer !== 0)
|
|
77
|
+
return closer;
|
|
65
78
|
const ts = b.totalStock - a.totalStock;
|
|
66
79
|
if (ts !== 0)
|
|
67
80
|
return ts;
|
|
@@ -73,6 +86,7 @@ export const findBestStoreForCartTool = {
|
|
|
73
86
|
allSufficient: fulfilledCount === input.items.length,
|
|
74
87
|
fulfilledCount,
|
|
75
88
|
totalCount: input.items.length,
|
|
89
|
+
...(distances.has(storeId) ? { distanceKm: distances.get(storeId) } : {}),
|
|
76
90
|
items,
|
|
77
91
|
}));
|
|
78
92
|
return { content: [{ type: "text", text: JSON.stringify(ranked, null, 2) }] };
|
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
import { FindBestStoreInput } from "../schemas/index.js";
|
|
2
2
|
import { getStoreStock, projectStock } from "../services/ikea.js";
|
|
3
|
-
import {
|
|
3
|
+
import { storeLabel } from "../data/stores.js";
|
|
4
|
+
import { resolveTargets } from "./targets.js";
|
|
4
5
|
import { pMap } from "../utils/http.js";
|
|
6
|
+
import { COUNTRY_CODES } from "../data/countries.js";
|
|
5
7
|
export const findBestStoreForItemTool = {
|
|
6
8
|
name: "find_best_store_for_item",
|
|
7
|
-
description: "Find stores with the highest in-stock quantity for an item. Returns up to maxResults stores sorted by quantity descending. Optionally filter by countryCode ('US' or '
|
|
9
|
+
description: "Find stores with the highest in-stock quantity for an item. Returns up to maxResults stores sorted by quantity descending, or nearest first when `near` is given. Optionally filter by countryCode ('US', 'CA' or 'NO'). Explicit storeIds take precedence over countryCode.",
|
|
8
10
|
inputSchema: {
|
|
9
11
|
type: "object",
|
|
10
12
|
properties: {
|
|
11
13
|
itemNo: { type: "string" },
|
|
12
14
|
storeIds: { type: "array", items: { type: "string" } },
|
|
13
15
|
maxResults: { type: "number", default: 3 },
|
|
14
|
-
countryCode: { type: "string", enum: [
|
|
16
|
+
countryCode: { type: "string", enum: [...COUNTRY_CODES] },
|
|
17
|
+
near: {
|
|
18
|
+
type: "object",
|
|
19
|
+
description: "Search only the stores closest to this point (the user's city or address). Use this instead of countryCode when you know where the user is.",
|
|
20
|
+
properties: { latitude: { type: "number" }, longitude: { type: "number" } },
|
|
21
|
+
required: ["latitude", "longitude"],
|
|
22
|
+
},
|
|
23
|
+
nearestStores: { type: "number", default: 5, description: "With near: how many of the closest stores to search (1–20)." },
|
|
24
|
+
maxDistanceKm: { type: "number", description: "Leave out stores farther than this." },
|
|
15
25
|
minQuantity: { type: "number" },
|
|
16
26
|
},
|
|
17
27
|
required: ["itemNo"],
|
|
18
28
|
},
|
|
19
29
|
async handler(rawInput) {
|
|
20
30
|
const input = FindBestStoreInput.parse(rawInput);
|
|
21
|
-
const targetIds = input
|
|
22
|
-
?? (input.countryCode ? storeIdsByCountry(input.countryCode) : Object.keys(STORE_LABELS));
|
|
31
|
+
const { storeIds: targetIds, distances } = resolveTargets(input);
|
|
23
32
|
const results = await pMap(targetIds, async (storeId) => {
|
|
24
33
|
const data = await getStoreStock(input.itemNo, storeId, "us");
|
|
25
34
|
const stock = projectStock(data);
|
|
@@ -30,6 +39,10 @@ export const findBestStoreForItemTool = {
|
|
|
30
39
|
r.stock.quantity !== null &&
|
|
31
40
|
(input.minQuantity === undefined || r.stock.quantity >= input.minQuantity))
|
|
32
41
|
.sort((a, b) => {
|
|
42
|
+
// With `near`, any store that has the item is good enough, so the closest comes first.
|
|
43
|
+
const closer = (distances.get(a.storeId) ?? 0) - (distances.get(b.storeId) ?? 0);
|
|
44
|
+
if (closer !== 0)
|
|
45
|
+
return closer;
|
|
33
46
|
const diff = (b.stock.quantity ?? 0) - (a.stock.quantity ?? 0);
|
|
34
47
|
return diff !== 0 ? diff : a.storeId.localeCompare(b.storeId);
|
|
35
48
|
})
|
|
@@ -40,6 +53,7 @@ export const findBestStoreForItemTool = {
|
|
|
40
53
|
availableForCashCarry: stock.availableForCashCarry,
|
|
41
54
|
quantity: stock.quantity,
|
|
42
55
|
messageType: stock.messageType,
|
|
56
|
+
...(distances.has(storeId) ? { distanceKm: distances.get(storeId) } : {}),
|
|
43
57
|
}));
|
|
44
58
|
return { content: [{ type: "text", text: JSON.stringify(ranked, null, 2) }] };
|
|
45
59
|
},
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FindNearbyStoresInput } from "../schemas/index.js";
|
|
2
|
+
import { nearestStores } from "../data/geo.js";
|
|
3
|
+
import { storeLabel } from "../data/stores.js";
|
|
4
|
+
import { COUNTRY_CODES } from "../data/countries.js";
|
|
5
|
+
export const findNearbyStoresTool = {
|
|
6
|
+
name: "find_nearby_stores",
|
|
7
|
+
description: "Find the IKEA stores closest to a place. Pass the latitude and longitude of the user's city or address (an approximate city-centre coordinate is fine). Returns stores nearest first with the straight-line distance in km. Use the returned storeId values with the stock tools, or pass the same coordinates as `near` to find_best_store_for_item / find_best_store_for_cart.",
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
latitude: { type: "number", minimum: -90, maximum: 90, description: "Latitude in decimal degrees, e.g. 43.65 for Toronto." },
|
|
12
|
+
longitude: { type: "number", minimum: -180, maximum: 180, description: "Longitude in decimal degrees, e.g. -79.38 for Toronto." },
|
|
13
|
+
countryCode: { type: "string", enum: [...COUNTRY_CODES], description: "Only return stores in this country." },
|
|
14
|
+
maxResults: { type: "number", default: 5, description: "How many stores to return (1–20)." },
|
|
15
|
+
maxDistanceKm: { type: "number", description: "Leave out stores farther than this." },
|
|
16
|
+
},
|
|
17
|
+
required: ["latitude", "longitude"],
|
|
18
|
+
},
|
|
19
|
+
async handler(rawInput) {
|
|
20
|
+
const input = FindNearbyStoresInput.parse(rawInput);
|
|
21
|
+
const stores = nearestStores({ lat: input.latitude, lon: input.longitude }, { countryCode: input.countryCode, limit: input.maxResults, maxDistanceKm: input.maxDistanceKm }).map((s) => ({
|
|
22
|
+
storeId: s.storeId,
|
|
23
|
+
storeLabel: storeLabel(s.storeId) ?? s.storeId,
|
|
24
|
+
countryCode: s.countryCode,
|
|
25
|
+
distanceKm: s.distanceKm,
|
|
26
|
+
}));
|
|
27
|
+
return { content: [{ type: "text", text: JSON.stringify({ stores }, null, 2) }] };
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -7,8 +7,8 @@ export const getProductDetailsTool = {
|
|
|
7
7
|
type: "object",
|
|
8
8
|
properties: {
|
|
9
9
|
itemNo: { type: "string" },
|
|
10
|
-
countryCode: { type: "string", default: "
|
|
11
|
-
langCode: { type: "string",
|
|
10
|
+
countryCode: { type: "string", default: "US", description: "Two-letter country code of the IKEA market to price and localise the product in, e.g. US, CA, NO." },
|
|
11
|
+
langCode: { type: "string", description: "Two-letter language code. Defaults to the country's language: en for US and CA, no for NO." },
|
|
12
12
|
},
|
|
13
13
|
required: ["itemNo"],
|
|
14
14
|
},
|
|
@@ -1,29 +1,24 @@
|
|
|
1
1
|
import { ListStoresInput } from "../schemas/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { STORES, storeLabelFor } from "../data/stores.js";
|
|
3
|
+
import { COUNTRY_CODES } from "../data/countries.js";
|
|
3
4
|
export const listStoresTool = {
|
|
4
5
|
name: "list_stores",
|
|
5
|
-
description: "List known IKEA store IDs and display names. Use countryCode ('US' or '
|
|
6
|
+
description: "List known IKEA store IDs and display names. Use countryCode ('US', 'CA' or 'NO') to filter by country, or omit to return all stores. Use the returned storeId values with the stock tools.",
|
|
6
7
|
inputSchema: {
|
|
7
8
|
type: "object",
|
|
8
9
|
properties: {
|
|
9
|
-
countryCode: { type: "string", enum: [
|
|
10
|
+
countryCode: { type: "string", enum: [...COUNTRY_CODES] },
|
|
10
11
|
},
|
|
11
12
|
},
|
|
12
13
|
async handler(rawInput) {
|
|
13
14
|
const input = ListStoresInput.parse(rawInput);
|
|
14
|
-
const
|
|
15
|
-
?
|
|
16
|
-
:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
:
|
|
21
|
-
? new Set()
|
|
22
|
-
: new Set(storeIdsByCountry("CA"));
|
|
23
|
-
const stores = storeIds.map((storeId) => ({
|
|
24
|
-
storeId,
|
|
25
|
-
storeLabel: storeLabel(storeId) ?? storeId,
|
|
26
|
-
countryCode: caSet.has(storeId) ? "CA" : "US",
|
|
15
|
+
const records = input.countryCode
|
|
16
|
+
? STORES.filter((s) => s.countryCode === input.countryCode)
|
|
17
|
+
: STORES;
|
|
18
|
+
const stores = records.map((s) => ({
|
|
19
|
+
storeId: s.storeId,
|
|
20
|
+
storeLabel: storeLabelFor(s),
|
|
21
|
+
countryCode: s.countryCode,
|
|
27
22
|
}));
|
|
28
23
|
return { content: [{ type: "text", text: JSON.stringify({ stores }, null, 2) }] };
|
|
29
24
|
},
|
|
@@ -7,8 +7,8 @@ export const searchProductsTool = {
|
|
|
7
7
|
type: "object",
|
|
8
8
|
properties: {
|
|
9
9
|
query: { type: "string" },
|
|
10
|
-
countryCode: { type: "string", default: "
|
|
11
|
-
langCode: { type: "string",
|
|
10
|
+
countryCode: { type: "string", default: "US", description: "Two-letter country code of the IKEA market to search, e.g. US, CA, NO." },
|
|
11
|
+
langCode: { type: "string", description: "Two-letter language code. Defaults to the country's language: en for US and CA, no for NO." },
|
|
12
12
|
size: { type: "number", default: 10 },
|
|
13
13
|
},
|
|
14
14
|
required: ["query"],
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { nearestStores } from "../data/geo.js";
|
|
2
|
+
import { allStoreIds, storeIdsByCountry } from "../data/stores.js";
|
|
3
|
+
/**
|
|
4
|
+
* The stores a ranking tool should search. Explicit storeIds win; then the stores closest to `near`
|
|
5
|
+
* (within countryCode when both are given; with `near`, `maxDistanceKm` leaves out stores farther than that,
|
|
6
|
+
* and it is ignored with explicit storeIds); then every store in countryCode; then the whole catalog.
|
|
7
|
+
*/
|
|
8
|
+
export function resolveTargets(input) {
|
|
9
|
+
if (input.storeIds)
|
|
10
|
+
return { storeIds: input.storeIds, distances: new Map() };
|
|
11
|
+
if (input.near) {
|
|
12
|
+
const nearest = nearestStores({ lat: input.near.latitude, lon: input.near.longitude }, { countryCode: input.countryCode, limit: input.nearestStores, maxDistanceKm: input.maxDistanceKm });
|
|
13
|
+
return { storeIds: nearest.map((s) => s.storeId), distances: new Map(nearest.map((s) => [s.storeId, s.distanceKm])) };
|
|
14
|
+
}
|
|
15
|
+
return { storeIds: input.countryCode ? storeIdsByCountry(input.countryCode) : allStoreIds(), distances: new Map() };
|
|
16
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ZodError } from "zod";
|
|
2
|
+
function describeIssues(err) {
|
|
3
|
+
return err.issues
|
|
4
|
+
.map((issue) => {
|
|
5
|
+
const where = issue.path.length ? issue.path.join(".") : "input";
|
|
6
|
+
return `${where}: ${issue.message}`;
|
|
7
|
+
})
|
|
8
|
+
.join("; ");
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Turns anything a tool handler threw into a tool result with isError set.
|
|
12
|
+
* Invalid input names the field so the model can correct the call; anything else is reported as an upstream failure.
|
|
13
|
+
*/
|
|
14
|
+
export function toToolError(toolName, err) {
|
|
15
|
+
let text;
|
|
16
|
+
if (err instanceof ZodError) {
|
|
17
|
+
text = `Invalid input for ${toolName}: ${describeIssues(err)}. Fix the arguments and call the tool again.`;
|
|
18
|
+
}
|
|
19
|
+
else if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
20
|
+
text = `${toolName} failed: the IKEA API did not answer in time. Try again shortly.`;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
24
|
+
text = `${toolName} failed: ${message}. The IKEA API may be unavailable or may have changed; try again shortly.`;
|
|
25
|
+
}
|
|
26
|
+
return { isError: true, content: [{ type: "text", text }] };
|
|
27
|
+
}
|
package/dist/utils/http.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
const RETRY_DELAY_MS = 500;
|
|
2
|
+
/** No single request to IKEA may hang a tool call: give up after this long. */
|
|
3
|
+
export const REQUEST_TIMEOUT_MS = 10_000;
|
|
2
4
|
function shouldRetry(status) {
|
|
3
5
|
return status === 429 || status >= 500;
|
|
4
6
|
}
|
|
5
7
|
async function attempt(url, headers) {
|
|
6
|
-
const res = await fetch(url, {
|
|
8
|
+
const res = await fetch(url, {
|
|
9
|
+
headers: { Accept: "application/json", ...headers },
|
|
10
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
11
|
+
});
|
|
7
12
|
if (!res.ok) {
|
|
8
13
|
if (shouldRetry(res.status)) {
|
|
9
14
|
const retryAfter = Number(res.headers.get("Retry-After") ?? 0) * 1000;
|
package/package.json
CHANGED
|
@@ -1,53 +1,59 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "ikea-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"main": "dist/index.js",
|
|
8
|
-
"bin": {
|
|
9
|
-
"ikea-mcp": "dist/index.js"
|
|
10
|
-
},
|
|
11
|
-
"files": [
|
|
12
|
-
"dist/",
|
|
13
|
-
"Procfile"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "ikea-mcp",
|
|
3
|
+
"version": "1.10.0",
|
|
4
|
+
"description": "MCP server for IKEA product search and live in-store stock in the US, Canada and Norway: find the nearest store that has an item or a whole shopping list. Read-only, stdio and Streamable HTTP.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"ikea-mcp": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/",
|
|
13
|
+
"Procfile",
|
|
14
|
+
"CHANGELOG.md"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"ikea",
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"stock",
|
|
21
|
+
"inventory",
|
|
22
|
+
"mcp-server",
|
|
23
|
+
"claude",
|
|
24
|
+
"store-locator",
|
|
25
|
+
"availability"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/taejin5314/ikea-mcp.git"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/taejin5314/ikea-mcp#readme",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/taejin5314/ikea-mcp/issues"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc",
|
|
37
|
+
"dev": "tsx src/index.ts",
|
|
38
|
+
"dev:http": "tsx src/http.ts",
|
|
39
|
+
"start": "node dist/index.js",
|
|
40
|
+
"start:http": "node dist/http.js",
|
|
41
|
+
"smoke": "node smoke.mjs",
|
|
42
|
+
"smoke:http": "node smoke-http.mjs",
|
|
43
|
+
"test": "node --import tsx/esm --test test/unit.test.ts",
|
|
44
|
+
"typecheck": "tsc -p tsconfig.test.json",
|
|
45
|
+
"prepublishOnly": "npm run build && npm test"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=20"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
52
|
+
"zod": "^3.22.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^20.0.0",
|
|
56
|
+
"tsx": "^4.0.0",
|
|
57
|
+
"typescript": "^5.0.0"
|
|
58
|
+
}
|
|
59
|
+
}
|