good-eggs-mcp-server 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.
@@ -0,0 +1,612 @@
1
+ import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
2
+ import { SearchGrocerySchema, GetGroceryDetailsSchema, AddToCartSchema, GetPastOrderGroceriesSchema, AddFavoriteSchema, RemoveFavoriteSchema, RemoveFromCartSchema, } from './types.js';
3
+ // =============================================================================
4
+ // TOOL DESCRIPTIONS
5
+ // =============================================================================
6
+ const SEARCH_GROCERY_DESCRIPTION = `Search for groceries on Good Eggs.
7
+
8
+ Returns a list of grocery items matching your search query, including:
9
+ - Product URL (for use with other tools)
10
+ - Product name and brand
11
+ - Price information
12
+ - Any discounts/deals
13
+
14
+ **Example queries:**
15
+ - "organic apples"
16
+ - "milk"
17
+ - "chicken breast"
18
+ - "gluten free bread"`;
19
+ const GET_FAVORITES_DESCRIPTION = `Get the user's favorite/saved grocery items on Good Eggs.
20
+
21
+ Returns a list of all items the user has marked as favorites, including:
22
+ - Product URL (for use with other tools)
23
+ - Product name and brand
24
+ - Price information
25
+
26
+ Requires the user to be logged in.`;
27
+ const GET_GROCERY_DETAILS_DESCRIPTION = `Get detailed information about a specific grocery item.
28
+
29
+ Provide the Good Eggs URL of the item (from search results or favorites).
30
+ Returns comprehensive details including:
31
+ - Full product name and brand
32
+ - Current and original prices
33
+ - Product description
34
+ - Delivery availability dates
35
+
36
+ **Tip:** This tool checks if you're already on the product page to minimize navigation.`;
37
+ const ADD_TO_CART_DESCRIPTION = `Add a grocery item to your shopping cart.
38
+
39
+ Provide:
40
+ - grocery_url: The Good Eggs URL of the item to add
41
+ - quantity: Number of items to add (default: 1)
42
+
43
+ Returns confirmation of the item added and quantity.
44
+
45
+ **Tip:** This tool checks if you're already on the product page to minimize navigation.`;
46
+ const SEARCH_FREEBIE_GROCERIES_DESCRIPTION = `Search for free items ($0.00) on Good Eggs.
47
+
48
+ Checks the homepage and /fresh-picks page for items priced at $0.00.
49
+ These are typically promotional freebies offered by Good Eggs.
50
+
51
+ Returns a list of free items with their URLs for adding to cart.`;
52
+ const GET_PAST_ORDER_DATES_DESCRIPTION = `Get a list of past order dates from Good Eggs.
53
+
54
+ Returns a list of previous orders with:
55
+ - Order date
56
+ - Order total (if available)
57
+ - Number of items (if available)
58
+
59
+ Use these dates with get_past_order_groceries to see specific order contents.
60
+ Requires the user to be logged in.`;
61
+ const GET_PAST_ORDER_GROCERIES_DESCRIPTION = `Get the grocery items from a specific past order.
62
+
63
+ Provide the past_order_date (from get_list_of_past_order_dates) to see what was ordered.
64
+ Returns a list of items from that order including:
65
+ - Product URL
66
+ - Product name and brand
67
+ - Price at time of order
68
+
69
+ Useful for reordering frequently purchased items.
70
+ Requires the user to be logged in.`;
71
+ const ADD_FAVORITE_DESCRIPTION = `Add a grocery item to your favorites.
72
+
73
+ Provide the Good Eggs URL of the item to add to favorites.
74
+ If the item is already in favorites, it will let you know.
75
+
76
+ Returns confirmation of the action taken.`;
77
+ const REMOVE_FAVORITE_DESCRIPTION = `Remove a grocery item from your favorites.
78
+
79
+ Provide the Good Eggs URL of the item to remove from favorites.
80
+ If the item is not in favorites, it will let you know.
81
+
82
+ Returns confirmation of the action taken.`;
83
+ const REMOVE_FROM_CART_DESCRIPTION = `Remove a grocery item from your shopping cart.
84
+
85
+ Provide the Good Eggs URL of the item to remove from your cart.
86
+ Navigates to the cart and removes the specified item.
87
+
88
+ Returns confirmation of the removal or an error if the item is not in the cart.`;
89
+ export function createRegisterTools(clientFactory) {
90
+ // Create a single client instance that persists across calls
91
+ let client = null;
92
+ let isInitialized = false;
93
+ const getClient = async () => {
94
+ if (!client) {
95
+ client = clientFactory();
96
+ }
97
+ if (!isInitialized) {
98
+ await client.initialize();
99
+ isInitialized = true;
100
+ }
101
+ return client;
102
+ };
103
+ const tools = [
104
+ {
105
+ name: 'search_for_grocery',
106
+ description: SEARCH_GROCERY_DESCRIPTION,
107
+ inputSchema: {
108
+ type: 'object',
109
+ properties: {
110
+ query: {
111
+ type: 'string',
112
+ description: 'Search query for groceries (e.g., "organic apples", "milk", "bread")',
113
+ },
114
+ },
115
+ required: ['query'],
116
+ },
117
+ handler: async (args) => {
118
+ try {
119
+ const validated = SearchGrocerySchema.parse(args);
120
+ const goodEggsClient = await getClient();
121
+ const results = await goodEggsClient.searchGroceries(validated.query);
122
+ if (results.length === 0) {
123
+ return {
124
+ content: [
125
+ {
126
+ type: 'text',
127
+ text: `No groceries found for "${validated.query}". Try a different search term.`,
128
+ },
129
+ ],
130
+ };
131
+ }
132
+ const formattedResults = results
133
+ .map((item, i) => `${i + 1}. **${item.name}**\n Brand: ${item.brand || 'N/A'}\n Price: ${item.price || 'N/A'}${item.discount ? `\n Discount: ${item.discount}` : ''}\n URL: ${item.url}`)
134
+ .join('\n\n');
135
+ return {
136
+ content: [
137
+ {
138
+ type: 'text',
139
+ text: `Found ${results.length} groceries for "${validated.query}":\n\n${formattedResults}`,
140
+ },
141
+ ],
142
+ };
143
+ }
144
+ catch (error) {
145
+ return {
146
+ content: [
147
+ {
148
+ type: 'text',
149
+ text: `Error searching for groceries: ${error instanceof Error ? error.message : String(error)}`,
150
+ },
151
+ ],
152
+ isError: true,
153
+ };
154
+ }
155
+ },
156
+ },
157
+ {
158
+ name: 'get_favorites',
159
+ description: GET_FAVORITES_DESCRIPTION,
160
+ inputSchema: {
161
+ type: 'object',
162
+ properties: {},
163
+ },
164
+ handler: async () => {
165
+ try {
166
+ const goodEggsClient = await getClient();
167
+ const results = await goodEggsClient.getFavorites();
168
+ if (results.length === 0) {
169
+ return {
170
+ content: [
171
+ {
172
+ type: 'text',
173
+ text: 'No favorite items found. Add items to your favorites on Good Eggs to see them here.',
174
+ },
175
+ ],
176
+ };
177
+ }
178
+ const formattedResults = results
179
+ .map((item, i) => `${i + 1}. **${item.name}**\n Brand: ${item.brand || 'N/A'}\n Price: ${item.price || 'N/A'}\n URL: ${item.url}`)
180
+ .join('\n\n');
181
+ return {
182
+ content: [
183
+ {
184
+ type: 'text',
185
+ text: `Found ${results.length} favorite items:\n\n${formattedResults}`,
186
+ },
187
+ ],
188
+ };
189
+ }
190
+ catch (error) {
191
+ return {
192
+ content: [
193
+ {
194
+ type: 'text',
195
+ text: `Error getting favorites: ${error instanceof Error ? error.message : String(error)}`,
196
+ },
197
+ ],
198
+ isError: true,
199
+ };
200
+ }
201
+ },
202
+ },
203
+ {
204
+ name: 'get_grocery_details',
205
+ description: GET_GROCERY_DETAILS_DESCRIPTION,
206
+ inputSchema: {
207
+ type: 'object',
208
+ properties: {
209
+ grocery_url: {
210
+ type: 'string',
211
+ description: 'The Good Eggs URL of the grocery item to get details for',
212
+ },
213
+ },
214
+ required: ['grocery_url'],
215
+ },
216
+ handler: async (args) => {
217
+ try {
218
+ const validated = GetGroceryDetailsSchema.parse(args);
219
+ const goodEggsClient = await getClient();
220
+ const details = await goodEggsClient.getGroceryDetails(validated.grocery_url);
221
+ const lines = [
222
+ `**${details.name}**`,
223
+ `Brand: ${details.brand || 'N/A'}`,
224
+ `Price: ${details.price || 'N/A'}`,
225
+ ];
226
+ if (details.originalPrice) {
227
+ lines.push(`Original Price: ${details.originalPrice}`);
228
+ }
229
+ if (details.discount) {
230
+ lines.push(`Discount: ${details.discount}`);
231
+ }
232
+ if (details.description) {
233
+ lines.push(`\nDescription: ${details.description}`);
234
+ }
235
+ if (details.productDetails) {
236
+ lines.push(`\nProduct Details: ${details.productDetails}`);
237
+ }
238
+ if (details.availability && details.availability.length > 0) {
239
+ lines.push(`\nAvailable for delivery: ${details.availability.join(', ')}`);
240
+ }
241
+ lines.push(`\nURL: ${details.url}`);
242
+ return {
243
+ content: [
244
+ {
245
+ type: 'text',
246
+ text: lines.join('\n'),
247
+ },
248
+ ],
249
+ };
250
+ }
251
+ catch (error) {
252
+ return {
253
+ content: [
254
+ {
255
+ type: 'text',
256
+ text: `Error getting grocery details: ${error instanceof Error ? error.message : String(error)}`,
257
+ },
258
+ ],
259
+ isError: true,
260
+ };
261
+ }
262
+ },
263
+ },
264
+ {
265
+ name: 'add_to_cart',
266
+ description: ADD_TO_CART_DESCRIPTION,
267
+ inputSchema: {
268
+ type: 'object',
269
+ properties: {
270
+ grocery_url: {
271
+ type: 'string',
272
+ description: 'The Good Eggs URL of the grocery item to add to cart',
273
+ },
274
+ quantity: {
275
+ type: 'number',
276
+ description: 'Quantity to add (default: 1)',
277
+ },
278
+ },
279
+ required: ['grocery_url'],
280
+ },
281
+ handler: async (args) => {
282
+ try {
283
+ const validated = AddToCartSchema.parse(args);
284
+ const goodEggsClient = await getClient();
285
+ const result = await goodEggsClient.addToCart(validated.grocery_url, validated.quantity);
286
+ return {
287
+ content: [
288
+ {
289
+ type: 'text',
290
+ text: result.success ? result.message : `Failed to add to cart: ${result.message}`,
291
+ },
292
+ ],
293
+ isError: !result.success,
294
+ };
295
+ }
296
+ catch (error) {
297
+ return {
298
+ content: [
299
+ {
300
+ type: 'text',
301
+ text: `Error adding to cart: ${error instanceof Error ? error.message : String(error)}`,
302
+ },
303
+ ],
304
+ isError: true,
305
+ };
306
+ }
307
+ },
308
+ },
309
+ {
310
+ name: 'search_for_freebie_groceries',
311
+ description: SEARCH_FREEBIE_GROCERIES_DESCRIPTION,
312
+ inputSchema: {
313
+ type: 'object',
314
+ properties: {},
315
+ },
316
+ handler: async () => {
317
+ try {
318
+ const goodEggsClient = await getClient();
319
+ const results = await goodEggsClient.searchFreebieGroceries();
320
+ if (results.length === 0) {
321
+ return {
322
+ content: [
323
+ {
324
+ type: 'text',
325
+ text: 'No free items or deals currently available.',
326
+ },
327
+ ],
328
+ };
329
+ }
330
+ const formattedResults = results
331
+ .map((item, i) => `${i + 1}. **${item.name}**\n Brand: ${item.brand || 'N/A'}\n Price: ${item.price || 'N/A'}\n Discount: ${item.discount || 'N/A'}\n URL: ${item.url}`)
332
+ .join('\n\n');
333
+ return {
334
+ content: [
335
+ {
336
+ type: 'text',
337
+ text: `Found ${results.length} deals/freebies:\n\n${formattedResults}`,
338
+ },
339
+ ],
340
+ };
341
+ }
342
+ catch (error) {
343
+ return {
344
+ content: [
345
+ {
346
+ type: 'text',
347
+ text: `Error searching for freebies: ${error instanceof Error ? error.message : String(error)}`,
348
+ },
349
+ ],
350
+ isError: true,
351
+ };
352
+ }
353
+ },
354
+ },
355
+ {
356
+ name: 'get_list_of_past_order_dates',
357
+ description: GET_PAST_ORDER_DATES_DESCRIPTION,
358
+ inputSchema: {
359
+ type: 'object',
360
+ properties: {},
361
+ },
362
+ handler: async () => {
363
+ try {
364
+ const goodEggsClient = await getClient();
365
+ const orders = await goodEggsClient.getPastOrderDates();
366
+ if (orders.length === 0) {
367
+ return {
368
+ content: [
369
+ {
370
+ type: 'text',
371
+ text: 'No past orders found.',
372
+ },
373
+ ],
374
+ };
375
+ }
376
+ const formattedResults = orders
377
+ .map((order, i) => {
378
+ const parts = [`${i + 1}. **${order.date}**`];
379
+ if (order.total)
380
+ parts.push(` Total: ${order.total}`);
381
+ if (order.itemCount)
382
+ parts.push(` Items: ${order.itemCount}`);
383
+ return parts.join('\n');
384
+ })
385
+ .join('\n\n');
386
+ return {
387
+ content: [
388
+ {
389
+ type: 'text',
390
+ text: `Found ${orders.length} past orders:\n\n${formattedResults}`,
391
+ },
392
+ ],
393
+ };
394
+ }
395
+ catch (error) {
396
+ return {
397
+ content: [
398
+ {
399
+ type: 'text',
400
+ text: `Error getting past order dates: ${error instanceof Error ? error.message : String(error)}`,
401
+ },
402
+ ],
403
+ isError: true,
404
+ };
405
+ }
406
+ },
407
+ },
408
+ {
409
+ name: 'get_past_order_groceries',
410
+ description: GET_PAST_ORDER_GROCERIES_DESCRIPTION,
411
+ inputSchema: {
412
+ type: 'object',
413
+ properties: {
414
+ past_order_date: {
415
+ type: 'string',
416
+ description: 'The date of the past order to get groceries from (e.g., "2024-01-15")',
417
+ },
418
+ },
419
+ required: ['past_order_date'],
420
+ },
421
+ handler: async (args) => {
422
+ try {
423
+ const validated = GetPastOrderGroceriesSchema.parse(args);
424
+ const goodEggsClient = await getClient();
425
+ const results = await goodEggsClient.getPastOrderGroceries(validated.past_order_date);
426
+ if (results.length === 0) {
427
+ return {
428
+ content: [
429
+ {
430
+ type: 'text',
431
+ text: `No items found for order on ${validated.past_order_date}. Make sure the date matches exactly.`,
432
+ },
433
+ ],
434
+ };
435
+ }
436
+ const formattedResults = results
437
+ .map((item, i) => `${i + 1}. **${item.name}**\n Brand: ${item.brand || 'N/A'}\n Price: ${item.price || 'N/A'}\n URL: ${item.url}`)
438
+ .join('\n\n');
439
+ return {
440
+ content: [
441
+ {
442
+ type: 'text',
443
+ text: `Found ${results.length} items from order on ${validated.past_order_date}:\n\n${formattedResults}`,
444
+ },
445
+ ],
446
+ };
447
+ }
448
+ catch (error) {
449
+ return {
450
+ content: [
451
+ {
452
+ type: 'text',
453
+ text: `Error getting past order groceries: ${error instanceof Error ? error.message : String(error)}`,
454
+ },
455
+ ],
456
+ isError: true,
457
+ };
458
+ }
459
+ },
460
+ },
461
+ {
462
+ name: 'add_favorite',
463
+ description: ADD_FAVORITE_DESCRIPTION,
464
+ inputSchema: {
465
+ type: 'object',
466
+ properties: {
467
+ grocery_url: {
468
+ type: 'string',
469
+ description: 'The Good Eggs URL of the grocery item to add to favorites',
470
+ },
471
+ },
472
+ required: ['grocery_url'],
473
+ },
474
+ handler: async (args) => {
475
+ try {
476
+ const validated = AddFavoriteSchema.parse(args);
477
+ const goodEggsClient = await getClient();
478
+ const result = await goodEggsClient.addFavorite(validated.grocery_url);
479
+ return {
480
+ content: [
481
+ {
482
+ type: 'text',
483
+ text: result.success
484
+ ? result.message
485
+ : `Failed to add to favorites: ${result.message}`,
486
+ },
487
+ ],
488
+ isError: !result.success,
489
+ };
490
+ }
491
+ catch (error) {
492
+ return {
493
+ content: [
494
+ {
495
+ type: 'text',
496
+ text: `Error adding to favorites: ${error instanceof Error ? error.message : String(error)}`,
497
+ },
498
+ ],
499
+ isError: true,
500
+ };
501
+ }
502
+ },
503
+ },
504
+ {
505
+ name: 'remove_favorite',
506
+ description: REMOVE_FAVORITE_DESCRIPTION,
507
+ inputSchema: {
508
+ type: 'object',
509
+ properties: {
510
+ grocery_url: {
511
+ type: 'string',
512
+ description: 'The Good Eggs URL of the grocery item to remove from favorites',
513
+ },
514
+ },
515
+ required: ['grocery_url'],
516
+ },
517
+ handler: async (args) => {
518
+ try {
519
+ const validated = RemoveFavoriteSchema.parse(args);
520
+ const goodEggsClient = await getClient();
521
+ const result = await goodEggsClient.removeFavorite(validated.grocery_url);
522
+ return {
523
+ content: [
524
+ {
525
+ type: 'text',
526
+ text: result.success
527
+ ? result.message
528
+ : `Failed to remove from favorites: ${result.message}`,
529
+ },
530
+ ],
531
+ isError: !result.success,
532
+ };
533
+ }
534
+ catch (error) {
535
+ return {
536
+ content: [
537
+ {
538
+ type: 'text',
539
+ text: `Error removing from favorites: ${error instanceof Error ? error.message : String(error)}`,
540
+ },
541
+ ],
542
+ isError: true,
543
+ };
544
+ }
545
+ },
546
+ },
547
+ {
548
+ name: 'remove_from_cart',
549
+ description: REMOVE_FROM_CART_DESCRIPTION,
550
+ inputSchema: {
551
+ type: 'object',
552
+ properties: {
553
+ grocery_url: {
554
+ type: 'string',
555
+ description: 'The Good Eggs URL of the grocery item to remove from cart',
556
+ },
557
+ },
558
+ required: ['grocery_url'],
559
+ },
560
+ handler: async (args) => {
561
+ try {
562
+ const validated = RemoveFromCartSchema.parse(args);
563
+ const goodEggsClient = await getClient();
564
+ const result = await goodEggsClient.removeFromCart(validated.grocery_url);
565
+ return {
566
+ content: [
567
+ {
568
+ type: 'text',
569
+ text: result.success
570
+ ? result.message
571
+ : `Failed to remove from cart: ${result.message}`,
572
+ },
573
+ ],
574
+ isError: !result.success,
575
+ };
576
+ }
577
+ catch (error) {
578
+ return {
579
+ content: [
580
+ {
581
+ type: 'text',
582
+ text: `Error removing from cart: ${error instanceof Error ? error.message : String(error)}`,
583
+ },
584
+ ],
585
+ isError: true,
586
+ };
587
+ }
588
+ },
589
+ },
590
+ ];
591
+ return (server) => {
592
+ // List available tools
593
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
594
+ return {
595
+ tools: tools.map((tool) => ({
596
+ name: tool.name,
597
+ description: tool.description,
598
+ inputSchema: tool.inputSchema,
599
+ })),
600
+ };
601
+ });
602
+ // Handle tool calls
603
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
604
+ const { name, arguments: args } = request.params;
605
+ const tool = tools.find((t) => t.name === name);
606
+ if (!tool) {
607
+ throw new Error(`Unknown tool: ${name}`);
608
+ }
609
+ return await tool.handler(args);
610
+ });
611
+ };
612
+ }
@@ -0,0 +1,95 @@
1
+ import { z } from 'zod';
2
+ export interface GroceryItem {
3
+ url: string;
4
+ name: string;
5
+ brand: string;
6
+ price: string;
7
+ originalPrice?: string;
8
+ discount?: string;
9
+ unit?: string;
10
+ imageUrl?: string;
11
+ }
12
+ export interface GroceryDetails {
13
+ url: string;
14
+ name: string;
15
+ brand: string;
16
+ price: string;
17
+ originalPrice?: string;
18
+ discount?: string;
19
+ unit?: string;
20
+ description?: string;
21
+ productDetails?: string;
22
+ availability?: string[];
23
+ imageUrl?: string;
24
+ }
25
+ export interface PastOrder {
26
+ date: string;
27
+ orderNumber?: string;
28
+ total?: string;
29
+ itemCount?: number;
30
+ }
31
+ export interface CartResult {
32
+ success: boolean;
33
+ message: string;
34
+ itemName?: string;
35
+ quantity?: number;
36
+ }
37
+ export declare const SearchGrocerySchema: z.ZodObject<{
38
+ query: z.ZodString;
39
+ }, "strip", z.ZodTypeAny, {
40
+ query: string;
41
+ }, {
42
+ query: string;
43
+ }>;
44
+ export declare const GetGroceryDetailsSchema: z.ZodObject<{
45
+ grocery_url: z.ZodEffects<z.ZodString, string, string>;
46
+ }, "strip", z.ZodTypeAny, {
47
+ grocery_url: string;
48
+ }, {
49
+ grocery_url: string;
50
+ }>;
51
+ export declare const AddToCartSchema: z.ZodObject<{
52
+ grocery_url: z.ZodEffects<z.ZodString, string, string>;
53
+ quantity: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
54
+ }, "strip", z.ZodTypeAny, {
55
+ grocery_url: string;
56
+ quantity: number;
57
+ }, {
58
+ grocery_url: string;
59
+ quantity?: number | undefined;
60
+ }>;
61
+ export declare const GetPastOrderGroceriesSchema: z.ZodObject<{
62
+ past_order_date: z.ZodString;
63
+ }, "strip", z.ZodTypeAny, {
64
+ past_order_date: string;
65
+ }, {
66
+ past_order_date: string;
67
+ }>;
68
+ export declare const AddFavoriteSchema: z.ZodObject<{
69
+ grocery_url: z.ZodEffects<z.ZodString, string, string>;
70
+ }, "strip", z.ZodTypeAny, {
71
+ grocery_url: string;
72
+ }, {
73
+ grocery_url: string;
74
+ }>;
75
+ export declare const RemoveFavoriteSchema: z.ZodObject<{
76
+ grocery_url: z.ZodEffects<z.ZodString, string, string>;
77
+ }, "strip", z.ZodTypeAny, {
78
+ grocery_url: string;
79
+ }, {
80
+ grocery_url: string;
81
+ }>;
82
+ export declare const RemoveFromCartSchema: z.ZodObject<{
83
+ grocery_url: z.ZodEffects<z.ZodString, string, string>;
84
+ }, "strip", z.ZodTypeAny, {
85
+ grocery_url: string;
86
+ }, {
87
+ grocery_url: string;
88
+ }>;
89
+ export interface GoodEggsConfig {
90
+ username: string;
91
+ password: string;
92
+ headless: boolean;
93
+ timeout: number;
94
+ }
95
+ //# sourceMappingURL=types.d.ts.map