good-eggs-mcp-server 0.1.6 → 0.1.8

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 CHANGED
@@ -27,23 +27,14 @@ MCP server for Good Eggs grocery shopping automation using Playwright. Search fo
27
27
  | `add_favorite` | Add a product to your favorites |
28
28
  | `remove_favorite` | Remove a product from your favorites |
29
29
 
30
- ## Quick Start
30
+ ## Setup
31
31
 
32
32
  ### Prerequisites
33
33
 
34
34
  - Node.js 18+
35
35
  - A Good Eggs account (create one at [goodeggs.com](https://www.goodeggs.com))
36
36
 
37
- ### Installation
38
-
39
- ```bash
40
- npm install
41
- npm run build
42
- ```
43
-
44
- ### Configuration
45
-
46
- Set the following environment variables:
37
+ ### Environment Variables
47
38
 
48
39
  | Variable | Required | Description | Default |
49
40
  | -------------------- | -------- | ------------------------------- | ------- |
@@ -52,13 +43,21 @@ Set the following environment variables:
52
43
  | `HEADLESS` | No | Run browser in headless mode | `true` |
53
44
  | `TIMEOUT` | No | Browser operation timeout (ms) | `30000` |
54
45
 
55
- ### Claude Desktop Configuration
46
+ ### Claude Desktop
47
+
48
+ Make sure you have your Good Eggs account credentials ready.
49
+
50
+ Then proceed to the setup instructions below. If this is your first time using MCP Servers, you'll want to make sure you have the [Claude Desktop application](https://claude.ai/download) and follow the [official MCP setup instructions](https://modelcontextprotocol.io/quickstart/user).
51
+
52
+ #### Manual Setup
53
+
54
+ You're going to need Node working on your machine so you can run `npx` commands in your terminal. If you don't have Node, you can install it from [nodejs.org](https://nodejs.org/en/download).
56
55
 
57
- Add to your `claude_desktop_config.json`:
56
+ macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
58
57
 
59
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
58
+ Windows: `%APPDATA%\Claude\claude_desktop_config.json`
60
59
 
61
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
60
+ Modify your `claude_desktop_config.json` file to add the following:
62
61
 
63
62
  ```json
64
63
  {
@@ -75,7 +74,7 @@ Add to your `claude_desktop_config.json`:
75
74
  }
76
75
  ```
77
76
 
78
- Restart Claude Desktop to connect.
77
+ Restart Claude Desktop and you should be ready to go!
79
78
 
80
79
  ## Usage Examples
81
80
 
@@ -9,8 +9,16 @@
9
9
  * Note: The mock client is defined inline because TypeScript's rootDir
10
10
  * constraints prevent importing from the tests/ directory.
11
11
  */
12
+ import { readFileSync } from 'fs';
13
+ import { dirname, join } from 'path';
14
+ import { fileURLToPath } from 'url';
12
15
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
13
16
  import { createMCPServer } from '../shared/index.js';
17
+ // Read version from package.json
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ const packageJsonPath = join(__dirname, '..', 'package.json');
20
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
21
+ const VERSION = packageJson.version;
14
22
  /**
15
23
  * Creates a mock Good Eggs client for integration testing.
16
24
  * This is kept inline to avoid TypeScript rootDir issues with importing from tests/.
@@ -153,7 +161,7 @@ async function main() {
153
161
  const transport = new StdioServerTransport();
154
162
  // Create client factory that returns our mock
155
163
  const clientFactory = () => createMockGoodEggsClient();
156
- const { server, registerHandlers } = createMCPServer();
164
+ const { server, registerHandlers } = createMCPServer({ version: VERSION });
157
165
  await registerHandlers(server, clientFactory);
158
166
  await server.connect(transport);
159
167
  }
package/build/index.js CHANGED
@@ -1,7 +1,15 @@
1
1
  #!/usr/bin/env node
2
+ import { readFileSync } from 'fs';
3
+ import { dirname, join } from 'path';
4
+ import { fileURLToPath } from 'url';
2
5
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
6
  import { createMCPServer } from '../shared/index.js';
4
7
  import { logServerStart, logError, logWarning } from '../shared/logging.js';
8
+ // Read version from package.json
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const packageJsonPath = join(__dirname, '..', 'package.json');
11
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
12
+ const VERSION = packageJson.version;
5
13
  // =============================================================================
6
14
  // ENVIRONMENT VALIDATION
7
15
  // =============================================================================
@@ -70,7 +78,9 @@ async function main() {
70
78
  // Step 1: Validate environment variables
71
79
  validateEnvironment();
72
80
  // Step 2: Create server using factory
73
- const { server, registerHandlers, cleanup, startBackgroundLogin } = createMCPServer();
81
+ const { server, registerHandlers, cleanup, startBackgroundLogin } = createMCPServer({
82
+ version: VERSION,
83
+ });
74
84
  // Step 3: Register all handlers (tools)
75
85
  await registerHandlers(server);
76
86
  // Step 4: Set up graceful shutdown
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "good-eggs-mcp-server",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { createMCPServer, GoodEggsClient, type IGoodEggsClient, type ClientFactory, } from './server.js';
1
+ export { createMCPServer, GoodEggsClient, type IGoodEggsClient, type ClientFactory, type CreateMCPServerOptions, } from './server.js';
2
2
  export { createRegisterTools } from './tools.js';
3
3
  export type { GroceryItem, GroceryDetails, PastOrder, CartResult, CartItem, GoodEggsConfig, } from './types.js';
4
4
  export { logServerStart, logError, logWarning, logDebug } from './logging.js';
@@ -100,7 +100,10 @@ export type ClientFactory = () => IGoodEggsClient;
100
100
  * @param error The error that caused login to fail
101
101
  */
102
102
  export type LoginFailedCallback = (error: Error) => void;
103
- export declare function createMCPServer(): {
103
+ export interface CreateMCPServerOptions {
104
+ version: string;
105
+ }
106
+ export declare function createMCPServer(options: CreateMCPServerOptions): {
104
107
  server: Server<{
105
108
  method: string;
106
109
  params?: {
@@ -108,6 +111,9 @@ export declare function createMCPServer(): {
108
111
  _meta?: {
109
112
  [x: string]: unknown;
110
113
  progressToken?: string | number | undefined;
114
+ "io.modelcontextprotocol/related-task"?: {
115
+ taskId: string;
116
+ } | undefined;
111
117
  } | undefined;
112
118
  } | undefined;
113
119
  }, {
@@ -116,12 +122,20 @@ export declare function createMCPServer(): {
116
122
  [x: string]: unknown;
117
123
  _meta?: {
118
124
  [x: string]: unknown;
125
+ progressToken?: string | number | undefined;
126
+ "io.modelcontextprotocol/related-task"?: {
127
+ taskId: string;
128
+ } | undefined;
119
129
  } | undefined;
120
130
  } | undefined;
121
131
  }, {
122
132
  [x: string]: unknown;
123
133
  _meta?: {
124
134
  [x: string]: unknown;
135
+ progressToken?: string | number | undefined;
136
+ "io.modelcontextprotocol/related-task"?: {
137
+ taskId: string;
138
+ } | undefined;
125
139
  } | undefined;
126
140
  }>;
127
141
  registerHandlers: (server: Server, clientFactory?: ClientFactory) => Promise<void>;
package/shared/server.js CHANGED
@@ -559,24 +559,19 @@ export class GoodEggsClient {
559
559
  const nameEl = document.querySelector('h1, [class*="product-name"], [class*="title"]');
560
560
  return nameEl?.textContent?.trim() || 'Unknown item';
561
561
  });
562
- // Look for the favorite/heart button
563
- const favoriteButton = await page.$('button[aria-label*="favorite"], button[aria-label*="heart"], button:has([class*="heart"]), [class*="favorite"] button, button[class*="favorite"]');
564
- if (!favoriteButton) {
562
+ // Look for the favorite control - Good Eggs uses a div, not a button
563
+ const favoriteControl = await page.$('.product-detail__favorite-control');
564
+ if (!favoriteControl) {
565
565
  return {
566
566
  success: false,
567
567
  message: 'Could not find favorite button',
568
568
  itemName,
569
569
  };
570
570
  }
571
- // Check if already favorited (button might have "filled" or "active" state)
572
- const isAlreadyFavorited = await page.evaluate((btn) => {
573
- const classList = btn.className || '';
574
- const ariaPressed = btn.getAttribute('aria-pressed');
575
- return (classList.includes('active') ||
576
- classList.includes('filled') ||
577
- classList.includes('favorited') ||
578
- ariaPressed === 'true');
579
- }, favoriteButton);
571
+ // Check if already favorited by looking for 'favorited' class (vs 'not-favorited')
572
+ const isAlreadyFavorited = await page.evaluate((el) => {
573
+ return el.classList.contains('favorited');
574
+ }, favoriteControl);
580
575
  if (isAlreadyFavorited) {
581
576
  return {
582
577
  success: true,
@@ -584,7 +579,7 @@ export class GoodEggsClient {
584
579
  itemName,
585
580
  };
586
581
  }
587
- await favoriteButton.click();
582
+ await favoriteControl.click();
588
583
  await page.waitForTimeout(500);
589
584
  return {
590
585
  success: true,
@@ -609,24 +604,19 @@ export class GoodEggsClient {
609
604
  const nameEl = document.querySelector('h1, [class*="product-name"], [class*="title"]');
610
605
  return nameEl?.textContent?.trim() || 'Unknown item';
611
606
  });
612
- // Look for the favorite/heart button
613
- const favoriteButton = await page.$('button[aria-label*="favorite"], button[aria-label*="heart"], button:has([class*="heart"]), [class*="favorite"] button, button[class*="favorite"]');
614
- if (!favoriteButton) {
607
+ // Look for the favorite control - Good Eggs uses a div, not a button
608
+ const favoriteControl = await page.$('.product-detail__favorite-control');
609
+ if (!favoriteControl) {
615
610
  return {
616
611
  success: false,
617
612
  message: 'Could not find favorite button',
618
613
  itemName,
619
614
  };
620
615
  }
621
- // Check if already favorited (button might have "filled" or "active" state)
622
- const isFavorited = await page.evaluate((btn) => {
623
- const classList = btn.className || '';
624
- const ariaPressed = btn.getAttribute('aria-pressed');
625
- return (classList.includes('active') ||
626
- classList.includes('filled') ||
627
- classList.includes('favorited') ||
628
- ariaPressed === 'true');
629
- }, favoriteButton);
616
+ // Check if favorited by looking for 'favorited' class (vs 'not-favorited')
617
+ const isFavorited = await page.evaluate((el) => {
618
+ return el.classList.contains('favorited');
619
+ }, favoriteControl);
630
620
  if (!isFavorited) {
631
621
  return {
632
622
  success: true,
@@ -634,7 +624,7 @@ export class GoodEggsClient {
634
624
  itemName,
635
625
  };
636
626
  }
637
- await favoriteButton.click();
627
+ await favoriteControl.click();
638
628
  await page.waitForTimeout(500);
639
629
  return {
640
630
  success: true,
@@ -788,10 +778,10 @@ export class GoodEggsClient {
788
778
  return this.config;
789
779
  }
790
780
  }
791
- export function createMCPServer() {
781
+ export function createMCPServer(options) {
792
782
  const server = new Server({
793
783
  name: 'good-eggs-mcp-server',
794
- version: '0.1.0',
784
+ version: options.version,
795
785
  }, {
796
786
  capabilities: {
797
787
  tools: {},