@striderlabs/mcp-hm 0.1.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/README.md +141 -0
- package/dist/index.js +21921 -0
- package/package.json +44 -0
- package/server.json +20 -0
- package/src/auth.ts +114 -0
- package/src/browser.ts +820 -0
- package/src/index.ts +565 -0
- package/tsconfig.json +14 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
type Tool,
|
|
9
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
checkLoginStatus,
|
|
13
|
+
initiateLogin,
|
|
14
|
+
logout,
|
|
15
|
+
searchProducts,
|
|
16
|
+
getProductDetails,
|
|
17
|
+
getSizeGuide,
|
|
18
|
+
addToCart,
|
|
19
|
+
viewCart,
|
|
20
|
+
initiateCheckout,
|
|
21
|
+
findStores,
|
|
22
|
+
getMemberRewards,
|
|
23
|
+
trackOrder,
|
|
24
|
+
getWishlist,
|
|
25
|
+
addToWishlist,
|
|
26
|
+
browseSale,
|
|
27
|
+
closeBrowser,
|
|
28
|
+
} from "./browser.js";
|
|
29
|
+
|
|
30
|
+
const tools: Tool[] = [
|
|
31
|
+
// ── Auth ──────────────────────────────────────────────────────────────
|
|
32
|
+
{
|
|
33
|
+
name: "hm_status",
|
|
34
|
+
description:
|
|
35
|
+
"Check H&M login status and session information. Call this first to see if the user is already logged in.",
|
|
36
|
+
inputSchema: { type: "object", properties: {} },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "hm_login",
|
|
40
|
+
description:
|
|
41
|
+
"Initiate H&M login flow. Opens the H&M login page in a browser so the user can sign in. After logging in, call hm_status to confirm.",
|
|
42
|
+
inputSchema: { type: "object", properties: {} },
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "hm_logout",
|
|
46
|
+
description: "Log out of H&M and clear saved session data.",
|
|
47
|
+
inputSchema: { type: "object", properties: {} },
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// ── Search ────────────────────────────────────────────────────────────
|
|
51
|
+
{
|
|
52
|
+
name: "hm_search_products",
|
|
53
|
+
description:
|
|
54
|
+
"Search H&M for clothing, accessories, and home items. Returns a list of matching products with prices and links.",
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: {
|
|
58
|
+
query: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "Search term, e.g. 'black slim jeans', 'linen shirt', 'throw pillow'",
|
|
61
|
+
},
|
|
62
|
+
maxResults: {
|
|
63
|
+
type: "number",
|
|
64
|
+
description: "Maximum number of results to return (default: 10)",
|
|
65
|
+
},
|
|
66
|
+
category: {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "Filter by category, e.g. 'ladies', 'men', 'kids', 'home'",
|
|
69
|
+
},
|
|
70
|
+
sortBy: {
|
|
71
|
+
type: "string",
|
|
72
|
+
description: "Sort order: 'stock' | 'ascPrice' | 'descPrice' | 'newProduct'",
|
|
73
|
+
enum: ["stock", "ascPrice", "descPrice", "newProduct"],
|
|
74
|
+
},
|
|
75
|
+
onSaleOnly: {
|
|
76
|
+
type: "boolean",
|
|
77
|
+
description: "Only return sale/discounted items",
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
required: ["query"],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
// ── Product details ───────────────────────────────────────────────────
|
|
85
|
+
{
|
|
86
|
+
name: "hm_product_details",
|
|
87
|
+
description:
|
|
88
|
+
"Get full details for a specific H&M product: description, available sizes, colors, materials, care instructions, and fit info.",
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: "object",
|
|
91
|
+
properties: {
|
|
92
|
+
productUrl: {
|
|
93
|
+
type: "string",
|
|
94
|
+
description: "Product URL (from hm_search_products results) or H&M product path",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
required: ["productUrl"],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
// ── Size guide ────────────────────────────────────────────────────────
|
|
102
|
+
{
|
|
103
|
+
name: "hm_size_guide",
|
|
104
|
+
description:
|
|
105
|
+
"Get H&M size guide and fit recommendations for a given category (e.g. 'ladies tops', 'men trousers', 'kids 4-14Y').",
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: "object",
|
|
108
|
+
properties: {
|
|
109
|
+
category: {
|
|
110
|
+
type: "string",
|
|
111
|
+
description: "Category for the size guide, e.g. 'ladies', 'men', 'kids', 'babies'",
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
required: ["category"],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
// ── Cart ──────────────────────────────────────────────────────────────
|
|
119
|
+
{
|
|
120
|
+
name: "hm_add_to_cart",
|
|
121
|
+
description:
|
|
122
|
+
"Add an H&M product to the shopping bag. Specify size and color if known.",
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: "object",
|
|
125
|
+
properties: {
|
|
126
|
+
productUrl: {
|
|
127
|
+
type: "string",
|
|
128
|
+
description: "Product URL to add to bag",
|
|
129
|
+
},
|
|
130
|
+
size: {
|
|
131
|
+
type: "string",
|
|
132
|
+
description: "Size to select, e.g. 'S', 'M', 'L', '32', '10'",
|
|
133
|
+
},
|
|
134
|
+
color: {
|
|
135
|
+
type: "string",
|
|
136
|
+
description: "Color name or variant to select",
|
|
137
|
+
},
|
|
138
|
+
quantity: {
|
|
139
|
+
type: "number",
|
|
140
|
+
description: "Quantity to add (default: 1)",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
required: ["productUrl"],
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "hm_view_cart",
|
|
148
|
+
description: "View the current H&M shopping bag contents, including items, prices, and totals.",
|
|
149
|
+
inputSchema: { type: "object", properties: {} },
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: "hm_checkout",
|
|
153
|
+
description:
|
|
154
|
+
"Initiate the H&M checkout process. This navigates to checkout so the user can complete their order.",
|
|
155
|
+
inputSchema: { type: "object", properties: {} },
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
// ── Stores ────────────────────────────────────────────────────────────
|
|
159
|
+
{
|
|
160
|
+
name: "hm_find_stores",
|
|
161
|
+
description:
|
|
162
|
+
"Find H&M stores near a location. Optionally check in-store availability for a product.",
|
|
163
|
+
inputSchema: {
|
|
164
|
+
type: "object",
|
|
165
|
+
properties: {
|
|
166
|
+
location: {
|
|
167
|
+
type: "string",
|
|
168
|
+
description: "City, zip code, or address to search near",
|
|
169
|
+
},
|
|
170
|
+
productId: {
|
|
171
|
+
type: "string",
|
|
172
|
+
description: "Optional product ID to check in-store stock",
|
|
173
|
+
},
|
|
174
|
+
radius: {
|
|
175
|
+
type: "number",
|
|
176
|
+
description: "Search radius in miles (default: 25)",
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
required: ["location"],
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
// ── Member rewards ────────────────────────────────────────────────────
|
|
184
|
+
{
|
|
185
|
+
name: "hm_member_rewards",
|
|
186
|
+
description:
|
|
187
|
+
"View H&M Member rewards, points balance, tier status, and available vouchers. Requires login.",
|
|
188
|
+
inputSchema: { type: "object", properties: {} },
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
// ── Order tracking ────────────────────────────────────────────────────
|
|
192
|
+
{
|
|
193
|
+
name: "hm_track_order",
|
|
194
|
+
description:
|
|
195
|
+
"Track H&M orders. Returns order history with status and estimated delivery. Optionally filter by order ID.",
|
|
196
|
+
inputSchema: {
|
|
197
|
+
type: "object",
|
|
198
|
+
properties: {
|
|
199
|
+
orderId: {
|
|
200
|
+
type: "string",
|
|
201
|
+
description: "Optional order ID to filter results",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
// ── Wishlist ──────────────────────────────────────────────────────────
|
|
208
|
+
{
|
|
209
|
+
name: "hm_view_wishlist",
|
|
210
|
+
description: "View saved H&M wishlist items with availability and prices.",
|
|
211
|
+
inputSchema: { type: "object", properties: {} },
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: "hm_add_to_wishlist",
|
|
215
|
+
description: "Save an H&M product to the wishlist/favorites.",
|
|
216
|
+
inputSchema: {
|
|
217
|
+
type: "object",
|
|
218
|
+
properties: {
|
|
219
|
+
productUrl: {
|
|
220
|
+
type: "string",
|
|
221
|
+
description: "Product URL to save to wishlist",
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
required: ["productUrl"],
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
// ── Sale ──────────────────────────────────────────────────────────────
|
|
229
|
+
{
|
|
230
|
+
name: "hm_browse_sale",
|
|
231
|
+
description:
|
|
232
|
+
"Browse H&M sale and clearance items. Filter by category and sort by discount or price.",
|
|
233
|
+
inputSchema: {
|
|
234
|
+
type: "object",
|
|
235
|
+
properties: {
|
|
236
|
+
category: {
|
|
237
|
+
type: "string",
|
|
238
|
+
description: "Category to browse: 'ladies', 'men', 'kids', 'home', 'baby'",
|
|
239
|
+
},
|
|
240
|
+
maxResults: {
|
|
241
|
+
type: "number",
|
|
242
|
+
description: "Maximum number of results (default: 20)",
|
|
243
|
+
},
|
|
244
|
+
sortBy: {
|
|
245
|
+
type: "string",
|
|
246
|
+
description: "Sort: 'ascPrice' | 'descPrice' | 'newProduct'",
|
|
247
|
+
enum: ["ascPrice", "descPrice", "newProduct"],
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
const server = new Server(
|
|
255
|
+
{
|
|
256
|
+
name: "striderlabs-mcp-hm",
|
|
257
|
+
version: "0.1.0",
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
capabilities: { tools: {} },
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
265
|
+
|
|
266
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
267
|
+
const { name, arguments: args = {} } = request.params;
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
switch (name) {
|
|
271
|
+
// ── Auth ────────────────────────────────────────────────────────
|
|
272
|
+
case "hm_status": {
|
|
273
|
+
const status = await checkLoginStatus();
|
|
274
|
+
return {
|
|
275
|
+
content: [
|
|
276
|
+
{
|
|
277
|
+
type: "text",
|
|
278
|
+
text: status.isLoggedIn
|
|
279
|
+
? `Logged in${status.userName ? ` as ${status.userName}` : ""}${status.userEmail ? ` (${status.userEmail})` : ""}`
|
|
280
|
+
: "Not logged in. Use hm_login to sign in.",
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
case "hm_login": {
|
|
287
|
+
const result = await initiateLogin();
|
|
288
|
+
return {
|
|
289
|
+
content: [
|
|
290
|
+
{
|
|
291
|
+
type: "text",
|
|
292
|
+
text: `Login page opened: ${result.url}\n\n${result.instructions}`,
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
case "hm_logout": {
|
|
299
|
+
await logout();
|
|
300
|
+
return {
|
|
301
|
+
content: [{ type: "text", text: "Logged out successfully. Session data cleared." }],
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ── Search ──────────────────────────────────────────────────────
|
|
306
|
+
case "hm_search_products": {
|
|
307
|
+
const { query, maxResults, category, sortBy, onSaleOnly } = args as {
|
|
308
|
+
query: string;
|
|
309
|
+
maxResults?: number;
|
|
310
|
+
category?: string;
|
|
311
|
+
sortBy?: string;
|
|
312
|
+
onSaleOnly?: boolean;
|
|
313
|
+
};
|
|
314
|
+
const results = await searchProducts(query, { maxResults, category, sortBy, onSaleOnly });
|
|
315
|
+
if (results.length === 0) {
|
|
316
|
+
return {
|
|
317
|
+
content: [{ type: "text", text: `No products found for "${query}". Try a different search term.` }],
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const text = results
|
|
321
|
+
.map(
|
|
322
|
+
(r, i) =>
|
|
323
|
+
`${i + 1}. **${r.name}**\n Price: ${r.price}${r.originalPrice ? ` (was ${r.originalPrice})` : ""}${r.isOnSale ? " 🔴 SALE" : ""}${r.color ? `\n Color: ${r.color}` : ""}${r.productUrl ? `\n URL: ${r.productUrl}` : ""}`
|
|
324
|
+
)
|
|
325
|
+
.join("\n\n");
|
|
326
|
+
return { content: [{ type: "text", text: `Found ${results.length} products for "${query}":\n\n${text}` }] };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ── Product details ─────────────────────────────────────────────
|
|
330
|
+
case "hm_product_details": {
|
|
331
|
+
const { productUrl } = args as { productUrl: string };
|
|
332
|
+
const details = await getProductDetails(productUrl);
|
|
333
|
+
const lines = [
|
|
334
|
+
`**${details.name}**`,
|
|
335
|
+
`Price: ${details.price}${details.originalPrice ? ` (was ${details.originalPrice})` : ""}`,
|
|
336
|
+
];
|
|
337
|
+
if (details.description) lines.push(`\nDescription: ${details.description}`);
|
|
338
|
+
if (details.fit) lines.push(`Fit: ${details.fit}`);
|
|
339
|
+
if (details.colors.length) lines.push(`Colors: ${details.colors.join(", ")}`);
|
|
340
|
+
if (details.sizes.length) lines.push(`All sizes: ${details.sizes.join(", ")}`);
|
|
341
|
+
if (details.availableSizes.length) lines.push(`Available sizes: ${details.availableSizes.join(", ")}`);
|
|
342
|
+
if (details.materials) lines.push(`\nMaterials: ${details.materials}`);
|
|
343
|
+
if (details.careInstructions) lines.push(`Care: ${details.careInstructions}`);
|
|
344
|
+
if (details.category) lines.push(`Category: ${details.category}`);
|
|
345
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ── Size guide ──────────────────────────────────────────────────
|
|
349
|
+
case "hm_size_guide": {
|
|
350
|
+
const { category } = args as { category: string };
|
|
351
|
+
const guide = await getSizeGuide(category);
|
|
352
|
+
if (guide.length === 0) {
|
|
353
|
+
return {
|
|
354
|
+
content: [
|
|
355
|
+
{
|
|
356
|
+
type: "text",
|
|
357
|
+
text: `No size guide data found for "${category}". Visit https://www2.hm.com/en_us/customer-service/size-guide.html for the full guide.`,
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
const text = guide
|
|
363
|
+
.map(
|
|
364
|
+
(g) =>
|
|
365
|
+
`**${g.garmentType}**\n${Object.entries(g.measurements)
|
|
366
|
+
.map(([k, v]) => ` ${k}: ${v}`)
|
|
367
|
+
.join("\n")}${g.tips ? `\nTips: ${g.tips}` : ""}`
|
|
368
|
+
)
|
|
369
|
+
.join("\n\n");
|
|
370
|
+
return { content: [{ type: "text", text: `H&M Size Guide - ${category}:\n\n${text}` }] };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ── Cart ────────────────────────────────────────────────────────
|
|
374
|
+
case "hm_add_to_cart": {
|
|
375
|
+
const { productUrl, size, color, quantity } = args as {
|
|
376
|
+
productUrl: string;
|
|
377
|
+
size?: string;
|
|
378
|
+
color?: string;
|
|
379
|
+
quantity?: number;
|
|
380
|
+
};
|
|
381
|
+
const result = await addToCart(productUrl, { size, color, quantity });
|
|
382
|
+
return { content: [{ type: "text", text: result.message }] };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
case "hm_view_cart": {
|
|
386
|
+
const cart = await viewCart();
|
|
387
|
+
if (cart.items.length === 0) {
|
|
388
|
+
return { content: [{ type: "text", text: "Your shopping bag is empty." }] };
|
|
389
|
+
}
|
|
390
|
+
const itemLines = cart.items.map(
|
|
391
|
+
(item, i) =>
|
|
392
|
+
`${i + 1}. ${item.name}${item.color ? ` - ${item.color}` : ""}${item.size ? ` / ${item.size}` : ""} × ${item.quantity} — ${item.price}`
|
|
393
|
+
);
|
|
394
|
+
const text = [
|
|
395
|
+
`Shopping Bag (${cart.itemCount} item${cart.itemCount !== 1 ? "s" : ""})`,
|
|
396
|
+
...itemLines,
|
|
397
|
+
"",
|
|
398
|
+
cart.subtotal ? `Subtotal: ${cart.subtotal}` : "",
|
|
399
|
+
cart.estimatedShipping ? `Shipping: ${cart.estimatedShipping}` : "",
|
|
400
|
+
cart.total ? `Total: ${cart.total}` : "",
|
|
401
|
+
]
|
|
402
|
+
.filter(Boolean)
|
|
403
|
+
.join("\n");
|
|
404
|
+
return { content: [{ type: "text", text }] };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
case "hm_checkout": {
|
|
408
|
+
const result = await initiateCheckout();
|
|
409
|
+
return { content: [{ type: "text", text: result.message + "\nCheckout URL: " + result.url }] };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ── Stores ──────────────────────────────────────────────────────
|
|
413
|
+
case "hm_find_stores": {
|
|
414
|
+
const { location, productId, radius } = args as {
|
|
415
|
+
location: string;
|
|
416
|
+
productId?: string;
|
|
417
|
+
radius?: number;
|
|
418
|
+
};
|
|
419
|
+
const stores = await findStores(location, { productId, radius });
|
|
420
|
+
if (stores.length === 0) {
|
|
421
|
+
return { content: [{ type: "text", text: `No H&M stores found near "${location}".` }] };
|
|
422
|
+
}
|
|
423
|
+
const text = stores
|
|
424
|
+
.map(
|
|
425
|
+
(s, i) =>
|
|
426
|
+
`${i + 1}. **${s.name}**\n ${s.address}${s.city ? `, ${s.city}` : ""}${s.distance ? ` (${s.distance})` : ""}${s.phone ? `\n Tel: ${s.phone}` : ""}${s.hours ? `\n Hours: ${s.hours}` : ""}`
|
|
427
|
+
)
|
|
428
|
+
.join("\n\n");
|
|
429
|
+
return { content: [{ type: "text", text: `H&M stores near ${location}:\n\n${text}` }] };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ── Member rewards ──────────────────────────────────────────────
|
|
433
|
+
case "hm_member_rewards": {
|
|
434
|
+
const member = await getMemberRewards();
|
|
435
|
+
const lines = ["**H&M Member Rewards**"];
|
|
436
|
+
if (member.name) lines.push(`Name: ${member.name}`);
|
|
437
|
+
if (member.email) lines.push(`Email: ${member.email}`);
|
|
438
|
+
if (member.tier) lines.push(`Status: ${member.tier}`);
|
|
439
|
+
if (member.points) lines.push(`Points: ${member.points}`);
|
|
440
|
+
if (member.nextReward) lines.push(`Next reward: ${member.nextReward}`);
|
|
441
|
+
if (member.rewards?.length) {
|
|
442
|
+
lines.push("\nAvailable rewards:");
|
|
443
|
+
member.rewards.forEach((r) => lines.push(` • ${r}`));
|
|
444
|
+
}
|
|
445
|
+
if (lines.length === 1) {
|
|
446
|
+
lines.push("No rewards data found. Make sure you are logged in (use hm_login).");
|
|
447
|
+
}
|
|
448
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ── Order tracking ──────────────────────────────────────────────
|
|
452
|
+
case "hm_track_order": {
|
|
453
|
+
const { orderId } = args as { orderId?: string };
|
|
454
|
+
const orders = await trackOrder(orderId);
|
|
455
|
+
if (orders.length === 0) {
|
|
456
|
+
return {
|
|
457
|
+
content: [
|
|
458
|
+
{
|
|
459
|
+
type: "text",
|
|
460
|
+
text: orderId
|
|
461
|
+
? `No order found with ID "${orderId}".`
|
|
462
|
+
: "No orders found. Make sure you are logged in.",
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
const text = orders
|
|
468
|
+
.map(
|
|
469
|
+
(o, i) =>
|
|
470
|
+
`${i + 1}. Order ${o.orderId || "(no ID)"} — ${o.date}\n Status: ${o.status}\n Total: ${o.total}${o.estimatedDelivery ? `\n Est. delivery: ${o.estimatedDelivery}` : ""}${o.trackingNumber ? `\n Tracking: ${o.trackingNumber}` : ""}${o.items?.length ? `\n Items: ${o.items.join(", ")}` : ""}`
|
|
471
|
+
)
|
|
472
|
+
.join("\n\n");
|
|
473
|
+
return { content: [{ type: "text", text: `Order history:\n\n${text}` }] };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ── Wishlist ────────────────────────────────────────────────────
|
|
477
|
+
case "hm_view_wishlist": {
|
|
478
|
+
const items = await getWishlist();
|
|
479
|
+
if (items.length === 0) {
|
|
480
|
+
return { content: [{ type: "text", text: "Your wishlist is empty." }] };
|
|
481
|
+
}
|
|
482
|
+
const text = items
|
|
483
|
+
.map(
|
|
484
|
+
(item, i) =>
|
|
485
|
+
`${i + 1}. **${item.name}** — ${item.price}${item.color ? ` (${item.color})` : ""} ${item.inStock ? "✓ In stock" : "✗ Out of stock"}${item.productUrl ? `\n ${item.productUrl}` : ""}`
|
|
486
|
+
)
|
|
487
|
+
.join("\n\n");
|
|
488
|
+
return { content: [{ type: "text", text: `Wishlist (${items.length} item${items.length !== 1 ? "s" : ""}):\n\n${text}` }] };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
case "hm_add_to_wishlist": {
|
|
492
|
+
const { productUrl } = args as { productUrl: string };
|
|
493
|
+
const result = await addToWishlist(productUrl);
|
|
494
|
+
return { content: [{ type: "text", text: result.message }] };
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// ── Sale ────────────────────────────────────────────────────────
|
|
498
|
+
case "hm_browse_sale": {
|
|
499
|
+
const { category, maxResults, sortBy } = args as {
|
|
500
|
+
category?: string;
|
|
501
|
+
maxResults?: number;
|
|
502
|
+
sortBy?: string;
|
|
503
|
+
};
|
|
504
|
+
const results = await browseSale({ category, maxResults, sortBy });
|
|
505
|
+
if (results.length === 0) {
|
|
506
|
+
return {
|
|
507
|
+
content: [
|
|
508
|
+
{
|
|
509
|
+
type: "text",
|
|
510
|
+
text: `No sale items found${category ? ` in "${category}"` : ""}. Visit https://www2.hm.com/en_us/sale for current promotions.`,
|
|
511
|
+
},
|
|
512
|
+
],
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
const text = results
|
|
516
|
+
.map(
|
|
517
|
+
(r, i) =>
|
|
518
|
+
`${i + 1}. **${r.name}**\n Price: ${r.price}${r.originalPrice ? ` (was ${r.originalPrice})` : ""}${r.discount ? ` — ${r.discount} off` : ""}${r.productUrl ? `\n ${r.productUrl}` : ""}`
|
|
519
|
+
)
|
|
520
|
+
.join("\n\n");
|
|
521
|
+
return {
|
|
522
|
+
content: [
|
|
523
|
+
{ type: "text", text: `H&M Sale${category ? ` — ${category}` : ""} (${results.length} items):\n\n${text}` },
|
|
524
|
+
],
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
default:
|
|
529
|
+
return {
|
|
530
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
531
|
+
isError: true,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
} catch (error) {
|
|
535
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
536
|
+
return {
|
|
537
|
+
content: [
|
|
538
|
+
{
|
|
539
|
+
type: "text",
|
|
540
|
+
text: `Error running ${name}: ${msg}\n\nTip: Make sure Chrome is installed and try again. If login-related, use hm_login first.`,
|
|
541
|
+
},
|
|
542
|
+
],
|
|
543
|
+
isError: true,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
async function main() {
|
|
549
|
+
const transport = new StdioServerTransport();
|
|
550
|
+
await server.connect(transport);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
main().catch((err) => {
|
|
554
|
+
console.error("H&M MCP server failed to start:", err);
|
|
555
|
+
process.exit(1);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
process.on("SIGINT", async () => {
|
|
559
|
+
await closeBrowser();
|
|
560
|
+
process.exit(0);
|
|
561
|
+
});
|
|
562
|
+
process.on("SIGTERM", async () => {
|
|
563
|
+
await closeBrowser();
|
|
564
|
+
process.exit(0);
|
|
565
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"declaration": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|