good-eggs-mcp-server 0.1.2 → 0.1.3

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/shared/server.js +95 -48
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "good-eggs-mcp-server",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "MCP server for Good Eggs grocery shopping with Playwright automation",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
package/shared/server.js CHANGED
@@ -389,19 +389,32 @@ export class GoodEggsClient {
389
389
  // Extract past order information
390
390
  const orders = await page.evaluate(() => {
391
391
  const orderList = [];
392
- // Look for order date elements
393
- const orderElements = document.querySelectorAll('[class*="order"], [class*="history"] > div, [class*="past-order"]');
394
- orderElements.forEach((el) => {
395
- const dateEl = el.querySelector('[class*="date"], time');
396
- const totalEl = el.querySelector('[class*="total"], [class*="price"]');
397
- const countEl = el.querySelector('[class*="count"], [class*="items"]');
392
+ // Good Eggs reorder page uses 'reorder-page__grid__header' class for order date headers
393
+ // Each header contains a <p> element with text like "Delivered Saturday, January 10th"
394
+ const headerElements = document.querySelectorAll('.reorder-page__grid__header');
395
+ headerElements.forEach((header) => {
396
+ const dateEl = header.querySelector('p');
398
397
  const dateText = dateEl?.textContent?.trim();
399
398
  if (!dateText)
400
399
  return;
400
+ // Skip non-order headers like "Based on your shopping" recommendations section
401
+ if (!dateText.includes('Delivered'))
402
+ return;
403
+ // Extract just the date part (e.g., "Saturday, January 10th" from "Delivered Saturday, January 10th")
404
+ const dateMatch = dateText.match(/Delivered\s+(.+)/);
405
+ const cleanDate = dateMatch ? dateMatch[1] : dateText;
406
+ // Count the products in this order section
407
+ // The products follow the header in the DOM as sibling elements with js-product-link
408
+ let itemCount = 0;
409
+ let sibling = header.nextElementSibling;
410
+ while (sibling && !sibling.classList.contains('reorder-page__grid__header')) {
411
+ const productLinks = sibling.querySelectorAll('a.js-product-link');
412
+ itemCount += productLinks.length;
413
+ sibling = sibling.nextElementSibling;
414
+ }
401
415
  orderList.push({
402
- date: dateText,
403
- total: totalEl?.textContent?.trim() || undefined,
404
- itemCount: countEl?.textContent ? parseInt(countEl.textContent) : undefined,
416
+ date: cleanDate,
417
+ itemCount: itemCount > 0 ? itemCount : undefined,
405
418
  });
406
419
  });
407
420
  return orderList;
@@ -420,52 +433,86 @@ export class GoodEggsClient {
420
433
  if (page.url().includes('/signin')) {
421
434
  throw new Error('Not logged in. Cannot access past orders.');
422
435
  }
423
- // Try to click on the specific order date
424
- const orderLink = await page.$(`text=${orderDate}`);
425
- if (orderLink) {
426
- await orderLink.click();
427
- await page.waitForTimeout(2000);
428
- }
429
- // Extract items from the order
430
- const items = await page.evaluate(() => {
436
+ // Extract items from the specific order date section
437
+ // The reorder page displays all orders with their products inline (not as separate pages)
438
+ const items = await page.evaluate((targetDate) => {
431
439
  const products = [];
432
440
  const seen = new Set();
433
- // Good Eggs uses 'js-product-link' class for product links
434
- const productElements = document.querySelectorAll('a.js-product-link');
435
- productElements.forEach((el) => {
436
- const link = el;
437
- const href = link.href;
438
- if (!href || seen.has(href) || href.includes('/reorder') || href.includes('/signin')) {
439
- return;
441
+ // Find all order headers
442
+ const headerElements = Array.from(document.querySelectorAll('.reorder-page__grid__header'));
443
+ // Find the header that matches our target date
444
+ let targetHeader = null;
445
+ for (const header of headerElements) {
446
+ const dateEl = header.querySelector('p');
447
+ const dateText = dateEl?.textContent?.trim() || '';
448
+ // Check if this header matches (could be exact match or partial match)
449
+ if (dateText.includes(targetDate) ||
450
+ targetDate.includes(dateText.replace('Delivered ', ''))) {
451
+ targetHeader = header;
452
+ break;
440
453
  }
441
- // Validate URL structure
442
- const urlPath = new URL(href).pathname;
443
- const segments = urlPath.split('/').filter((s) => s.length > 0);
444
- if (segments.length < 3) {
445
- return;
454
+ }
455
+ if (!targetHeader) {
456
+ return products;
457
+ }
458
+ // Get the parent container that holds all the products for this order
459
+ // Products are siblings of the header within the same grid section
460
+ const parent = targetHeader.parentElement;
461
+ if (!parent) {
462
+ return products;
463
+ }
464
+ // Find all product links within this section (between this header and the next one)
465
+ let foundHeader = false;
466
+ const children = Array.from(parent.children);
467
+ for (const child of children) {
468
+ if (child === targetHeader) {
469
+ foundHeader = true;
470
+ continue;
446
471
  }
447
- const container = link.closest('div[class*="product"], article, [class*="card"]') || link;
448
- const nameEl = container.querySelector('h2, h3, [class*="title"], [class*="name"]');
449
- const brandEl = container.querySelector('[class*="brand"], [class*="producer"]');
450
- const priceEl = container.querySelector('[class*="price"]');
451
- const imgEl = container.querySelector('img');
452
- let name = nameEl?.textContent?.trim();
453
- if (!name || name.length < 3) {
454
- name = link.textContent?.trim();
472
+ // Stop when we hit the next header
473
+ if (foundHeader && child.classList.contains('reorder-page__grid__header')) {
474
+ break;
455
475
  }
456
- if (!name || name.length < 3)
457
- return;
458
- seen.add(href);
459
- products.push({
460
- url: href,
461
- name: name,
462
- brand: brandEl?.textContent?.trim() || '',
463
- price: priceEl?.textContent?.trim() || '',
464
- imageUrl: imgEl?.src || undefined,
476
+ if (!foundHeader) {
477
+ continue;
478
+ }
479
+ // Extract product links from this child element
480
+ const productLinks = child.querySelectorAll('a.js-product-link');
481
+ productLinks.forEach((el) => {
482
+ const link = el;
483
+ const href = link.href;
484
+ if (!href || seen.has(href) || href.includes('/reorder') || href.includes('/signin')) {
485
+ return;
486
+ }
487
+ // Validate URL structure
488
+ const urlPath = new URL(href).pathname;
489
+ const segments = urlPath.split('/').filter((s) => s.length > 0);
490
+ if (segments.length < 3) {
491
+ return;
492
+ }
493
+ const container = link.closest('div[class*="product"], article, [class*="card"]') || link;
494
+ const nameEl = container.querySelector('h2, h3, [class*="title"], [class*="name"]');
495
+ const brandEl = container.querySelector('[class*="brand"], [class*="producer"]');
496
+ const priceEl = container.querySelector('[class*="price"]');
497
+ const imgEl = container.querySelector('img');
498
+ let name = nameEl?.textContent?.trim();
499
+ if (!name || name.length < 3) {
500
+ name = link.textContent?.trim();
501
+ }
502
+ if (!name || name.length < 3)
503
+ return;
504
+ seen.add(href);
505
+ products.push({
506
+ url: href,
507
+ name: name,
508
+ brand: brandEl?.textContent?.trim() || '',
509
+ price: priceEl?.textContent?.trim() || '',
510
+ imageUrl: imgEl?.src || undefined,
511
+ });
465
512
  });
466
- });
513
+ }
467
514
  return products;
468
- });
515
+ }, orderDate);
469
516
  return items;
470
517
  }
471
518
  async addFavorite(groceryUrl) {