@striderlabs/mcp-bjs 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/src/index.ts ADDED
@@ -0,0 +1,540 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import {
4
+ CallToolRequestSchema,
5
+ ListToolsRequestSchema,
6
+ } from "@modelcontextprotocol/sdk/types.js";
7
+
8
+ import {
9
+ checkLoginStatus,
10
+ initiateLogin,
11
+ logout,
12
+ searchProducts,
13
+ getProductDetails,
14
+ checkClubInventory,
15
+ addToCart,
16
+ viewCart,
17
+ removeFromCart,
18
+ startCheckout,
19
+ getMembershipInfo,
20
+ renewMembership,
21
+ getCoupons,
22
+ clipCoupon,
23
+ getMyClippedCoupons,
24
+ getOrderHistory,
25
+ getOrderDetails,
26
+ findClubs,
27
+ getGasPrices,
28
+ closeBrowser,
29
+ } from "./browser.js";
30
+
31
+ const server = new Server(
32
+ { name: "strider-bjs", version: "0.1.0" },
33
+ { capabilities: { tools: {} } }
34
+ );
35
+
36
+ // ── Tool definitions ──────────────────────────────────────────────────────────
37
+
38
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
39
+ tools: [
40
+ // Auth
41
+ {
42
+ name: "bjs_status",
43
+ description: "Check BJ's Wholesale Club login status and membership session.",
44
+ inputSchema: { type: "object", properties: {}, required: [] },
45
+ },
46
+ {
47
+ name: "bjs_login",
48
+ description: "Initiate BJ's login flow. Returns the login URL for manual sign-in.",
49
+ inputSchema: { type: "object", properties: {}, required: [] },
50
+ },
51
+ {
52
+ name: "bjs_logout",
53
+ description: "Log out of BJ's and clear saved session data.",
54
+ inputSchema: { type: "object", properties: {}, required: [] },
55
+ },
56
+ // Products
57
+ {
58
+ name: "bjs_search",
59
+ description:
60
+ "Search for products on BJ's Wholesale Club website. Returns product names, prices, and availability.",
61
+ inputSchema: {
62
+ type: "object",
63
+ properties: {
64
+ query: {
65
+ type: "string",
66
+ description: "Product search query (e.g. 'paper towels', 'samsung tv')",
67
+ },
68
+ maxResults: {
69
+ type: "number",
70
+ description: "Maximum number of results to return (default: 10)",
71
+ },
72
+ clubId: {
73
+ type: "string",
74
+ description: "Optional club ID to filter results for a specific club",
75
+ },
76
+ },
77
+ required: ["query"],
78
+ },
79
+ },
80
+ {
81
+ name: "bjs_product_details",
82
+ description:
83
+ "Get full product details and pricing for a specific BJ's product by URL.",
84
+ inputSchema: {
85
+ type: "object",
86
+ properties: {
87
+ productUrl: {
88
+ type: "string",
89
+ description: "Product page URL (from bjs_search results)",
90
+ },
91
+ },
92
+ required: ["productUrl"],
93
+ },
94
+ },
95
+ {
96
+ name: "bjs_check_inventory",
97
+ description:
98
+ "Check if a product is in stock at a specific BJ's club location.",
99
+ inputSchema: {
100
+ type: "object",
101
+ properties: {
102
+ productUrl: {
103
+ type: "string",
104
+ description: "Product page URL (from bjs_search results)",
105
+ },
106
+ clubId: {
107
+ type: "string",
108
+ description: "Optional club ID to check inventory at a specific club",
109
+ },
110
+ },
111
+ required: ["productUrl"],
112
+ },
113
+ },
114
+ // Cart & Checkout
115
+ {
116
+ name: "bjs_add_to_cart",
117
+ description: "Add a product to the BJ's shopping cart.",
118
+ inputSchema: {
119
+ type: "object",
120
+ properties: {
121
+ productUrl: {
122
+ type: "string",
123
+ description: "Product page URL (from bjs_search results)",
124
+ },
125
+ quantity: {
126
+ type: "number",
127
+ description: "Quantity to add (default: 1)",
128
+ },
129
+ },
130
+ required: ["productUrl"],
131
+ },
132
+ },
133
+ {
134
+ name: "bjs_view_cart",
135
+ description: "View all items in the current BJ's shopping cart with totals.",
136
+ inputSchema: { type: "object", properties: {}, required: [] },
137
+ },
138
+ {
139
+ name: "bjs_remove_from_cart",
140
+ description: "Remove an item from the BJ's shopping cart by name.",
141
+ inputSchema: {
142
+ type: "object",
143
+ properties: {
144
+ itemName: {
145
+ type: "string",
146
+ description: "Name (or partial name) of the item to remove",
147
+ },
148
+ },
149
+ required: ["itemName"],
150
+ },
151
+ },
152
+ {
153
+ name: "bjs_checkout",
154
+ description:
155
+ "Initiate checkout for the current cart. Supports in-club pickup, delivery, or shipping.",
156
+ inputSchema: {
157
+ type: "object",
158
+ properties: {
159
+ fulfillmentType: {
160
+ type: "string",
161
+ enum: ["pickup", "delivery", "ship"],
162
+ description: "How to receive the order: 'pickup' (in-club), 'delivery', or 'ship'",
163
+ },
164
+ },
165
+ required: ["fulfillmentType"],
166
+ },
167
+ },
168
+ // Membership
169
+ {
170
+ name: "bjs_membership_info",
171
+ description:
172
+ "Get BJ's membership details: membership number, type, expiry, and rewards.",
173
+ inputSchema: { type: "object", properties: {}, required: [] },
174
+ },
175
+ {
176
+ name: "bjs_membership_renew",
177
+ description: "Navigate to the BJ's membership renewal page.",
178
+ inputSchema: { type: "object", properties: {}, required: [] },
179
+ },
180
+ // Coupons
181
+ {
182
+ name: "bjs_coupons",
183
+ description: "Browse available BJ's digital coupons and offers.",
184
+ inputSchema: {
185
+ type: "object",
186
+ properties: {
187
+ category: {
188
+ type: "string",
189
+ description: "Optional category filter (e.g. 'grocery', 'electronics', 'household')",
190
+ },
191
+ },
192
+ required: [],
193
+ },
194
+ },
195
+ {
196
+ name: "bjs_clip_coupon",
197
+ description: "Clip a digital coupon to your BJ's membership card.",
198
+ inputSchema: {
199
+ type: "object",
200
+ properties: {
201
+ couponId: {
202
+ type: "string",
203
+ description: "Coupon ID from bjs_coupons results",
204
+ },
205
+ },
206
+ required: ["couponId"],
207
+ },
208
+ },
209
+ {
210
+ name: "bjs_my_coupons",
211
+ description: "View all coupons you have clipped to your BJ's membership card.",
212
+ inputSchema: { type: "object", properties: {}, required: [] },
213
+ },
214
+ // Orders
215
+ {
216
+ name: "bjs_order_history",
217
+ description: "View past BJ's orders.",
218
+ inputSchema: {
219
+ type: "object",
220
+ properties: {
221
+ maxOrders: {
222
+ type: "number",
223
+ description: "Maximum number of orders to return (default: 10)",
224
+ },
225
+ },
226
+ required: [],
227
+ },
228
+ },
229
+ {
230
+ name: "bjs_order_details",
231
+ description: "Get details for a specific BJ's order by order ID.",
232
+ inputSchema: {
233
+ type: "object",
234
+ properties: {
235
+ orderId: {
236
+ type: "string",
237
+ description: "Order ID from bjs_order_history results",
238
+ },
239
+ },
240
+ required: ["orderId"],
241
+ },
242
+ },
243
+ // Clubs & Gas
244
+ {
245
+ name: "bjs_find_clubs",
246
+ description: "Find BJ's Wholesale Club locations near a zip code.",
247
+ inputSchema: {
248
+ type: "object",
249
+ properties: {
250
+ zipCode: {
251
+ type: "string",
252
+ description: "Zip code to search near",
253
+ },
254
+ radius: {
255
+ type: "number",
256
+ description: "Search radius in miles (default: 25)",
257
+ },
258
+ },
259
+ required: ["zipCode"],
260
+ },
261
+ },
262
+ {
263
+ name: "bjs_gas_prices",
264
+ description:
265
+ "Get current gas prices at BJ's fuel stations. Includes regular, premium, and diesel prices.",
266
+ inputSchema: {
267
+ type: "object",
268
+ properties: {
269
+ zipCode: {
270
+ type: "string",
271
+ description: "Optional zip code to filter nearby gas stations",
272
+ },
273
+ },
274
+ required: [],
275
+ },
276
+ },
277
+ ],
278
+ }));
279
+
280
+ // ── Tool handlers ─────────────────────────────────────────────────────────────
281
+
282
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
283
+ const { name, arguments: args } = request.params;
284
+
285
+ try {
286
+ switch (name) {
287
+ // Auth
288
+ case "bjs_status": {
289
+ const status = await checkLoginStatus();
290
+ return {
291
+ content: [{ type: "text", text: JSON.stringify(status, null, 2) }],
292
+ };
293
+ }
294
+
295
+ case "bjs_login": {
296
+ const result = await initiateLogin();
297
+ return {
298
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
299
+ };
300
+ }
301
+
302
+ case "bjs_logout": {
303
+ const result = await logout();
304
+ return {
305
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
306
+ };
307
+ }
308
+
309
+ // Products
310
+ case "bjs_search": {
311
+ const { query, maxResults, clubId } = args as {
312
+ query: string;
313
+ maxResults?: number;
314
+ clubId?: string;
315
+ };
316
+ const results = await searchProducts(query, maxResults ?? 10, clubId);
317
+ return {
318
+ content: [
319
+ {
320
+ type: "text",
321
+ text: JSON.stringify(
322
+ { query, resultCount: results.length, results },
323
+ null,
324
+ 2
325
+ ),
326
+ },
327
+ ],
328
+ };
329
+ }
330
+
331
+ case "bjs_product_details": {
332
+ const { productUrl } = args as { productUrl: string };
333
+ const details = await getProductDetails(productUrl);
334
+ return {
335
+ content: [{ type: "text", text: JSON.stringify(details, null, 2) }],
336
+ };
337
+ }
338
+
339
+ case "bjs_check_inventory": {
340
+ const { productUrl, clubId } = args as {
341
+ productUrl: string;
342
+ clubId?: string;
343
+ };
344
+ const result = await checkClubInventory(productUrl, clubId);
345
+ return {
346
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
347
+ };
348
+ }
349
+
350
+ // Cart & Checkout
351
+ case "bjs_add_to_cart": {
352
+ const { productUrl, quantity } = args as {
353
+ productUrl: string;
354
+ quantity?: number;
355
+ };
356
+ const result = await addToCart(productUrl, quantity ?? 1);
357
+ return {
358
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
359
+ };
360
+ }
361
+
362
+ case "bjs_view_cart": {
363
+ const cart = await viewCart();
364
+ return {
365
+ content: [{ type: "text", text: JSON.stringify(cart, null, 2) }],
366
+ };
367
+ }
368
+
369
+ case "bjs_remove_from_cart": {
370
+ const { itemName } = args as { itemName: string };
371
+ const result = await removeFromCart(itemName);
372
+ return {
373
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
374
+ };
375
+ }
376
+
377
+ case "bjs_checkout": {
378
+ const { fulfillmentType } = args as {
379
+ fulfillmentType: "pickup" | "delivery" | "ship";
380
+ };
381
+ const result = await startCheckout(fulfillmentType);
382
+ return {
383
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
384
+ };
385
+ }
386
+
387
+ // Membership
388
+ case "bjs_membership_info": {
389
+ const info = await getMembershipInfo();
390
+ return {
391
+ content: [{ type: "text", text: JSON.stringify(info, null, 2) }],
392
+ };
393
+ }
394
+
395
+ case "bjs_membership_renew": {
396
+ const result = await renewMembership();
397
+ return {
398
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
399
+ };
400
+ }
401
+
402
+ // Coupons
403
+ case "bjs_coupons": {
404
+ const { category } = args as { category?: string };
405
+ const coupons = await getCoupons(category);
406
+ return {
407
+ content: [
408
+ {
409
+ type: "text",
410
+ text: JSON.stringify(
411
+ { count: coupons.length, coupons },
412
+ null,
413
+ 2
414
+ ),
415
+ },
416
+ ],
417
+ };
418
+ }
419
+
420
+ case "bjs_clip_coupon": {
421
+ const { couponId } = args as { couponId: string };
422
+ const result = await clipCoupon(couponId);
423
+ return {
424
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
425
+ };
426
+ }
427
+
428
+ case "bjs_my_coupons": {
429
+ const coupons = await getMyClippedCoupons();
430
+ return {
431
+ content: [
432
+ {
433
+ type: "text",
434
+ text: JSON.stringify(
435
+ { count: coupons.length, coupons },
436
+ null,
437
+ 2
438
+ ),
439
+ },
440
+ ],
441
+ };
442
+ }
443
+
444
+ // Orders
445
+ case "bjs_order_history": {
446
+ const { maxOrders } = args as { maxOrders?: number };
447
+ const orders = await getOrderHistory(maxOrders ?? 10);
448
+ return {
449
+ content: [
450
+ {
451
+ type: "text",
452
+ text: JSON.stringify(
453
+ { count: orders.length, orders },
454
+ null,
455
+ 2
456
+ ),
457
+ },
458
+ ],
459
+ };
460
+ }
461
+
462
+ case "bjs_order_details": {
463
+ const { orderId } = args as { orderId: string };
464
+ const details = await getOrderDetails(orderId);
465
+ return {
466
+ content: [{ type: "text", text: JSON.stringify(details, null, 2) }],
467
+ };
468
+ }
469
+
470
+ // Clubs & Gas
471
+ case "bjs_find_clubs": {
472
+ const { zipCode, radius } = args as {
473
+ zipCode: string;
474
+ radius?: number;
475
+ };
476
+ const clubs = await findClubs(zipCode, radius ?? 25);
477
+ return {
478
+ content: [
479
+ {
480
+ type: "text",
481
+ text: JSON.stringify(
482
+ { zipCode, radius: radius ?? 25, count: clubs.length, clubs },
483
+ null,
484
+ 2
485
+ ),
486
+ },
487
+ ],
488
+ };
489
+ }
490
+
491
+ case "bjs_gas_prices": {
492
+ const { zipCode } = args as { zipCode?: string };
493
+ const result = await getGasPrices(zipCode);
494
+ return {
495
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
496
+ };
497
+ }
498
+
499
+ default:
500
+ return {
501
+ content: [
502
+ { type: "text", text: `Unknown tool: ${name}` },
503
+ ],
504
+ isError: true,
505
+ };
506
+ }
507
+ } catch (error: unknown) {
508
+ const message = error instanceof Error ? error.message : String(error);
509
+ return {
510
+ content: [
511
+ {
512
+ type: "text",
513
+ text: JSON.stringify(
514
+ {
515
+ error: message,
516
+ tool: name,
517
+ hint: "Make sure you are logged in with bjs_login, then bjs_status to confirm.",
518
+ },
519
+ null,
520
+ 2
521
+ ),
522
+ },
523
+ ],
524
+ isError: true,
525
+ };
526
+ }
527
+ });
528
+
529
+ // ── Start ─────────────────────────────────────────────────────────────────────
530
+
531
+ server.onclose = async () => {
532
+ await closeBrowser();
533
+ };
534
+
535
+ async function main() {
536
+ const transport = new StdioServerTransport();
537
+ await server.connect(transport);
538
+ }
539
+
540
+ main().catch(console.error);
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
+ }