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 +15 -16
- package/build/index.integration-with-mock.js +9 -1
- package/build/index.js +11 -1
- package/package.json +1 -1
- package/shared/index.d.ts +1 -1
- package/shared/server.d.ts +15 -1
- package/shared/server.js +18 -28
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
|
-
##
|
|
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
|
-
###
|
|
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
|
|
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
|
-
|
|
56
|
+
macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
58
57
|
|
|
59
|
-
|
|
58
|
+
Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
60
59
|
|
|
61
|
-
|
|
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
|
|
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
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';
|
package/shared/server.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
563
|
-
const
|
|
564
|
-
if (!
|
|
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
|
|
572
|
-
const isAlreadyFavorited = await page.evaluate((
|
|
573
|
-
|
|
574
|
-
|
|
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
|
|
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
|
|
613
|
-
const
|
|
614
|
-
if (!
|
|
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
|
|
622
|
-
const isFavorited = await page.evaluate((
|
|
623
|
-
|
|
624
|
-
|
|
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
|
|
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:
|
|
784
|
+
version: options.version,
|
|
795
785
|
}, {
|
|
796
786
|
capabilities: {
|
|
797
787
|
tools: {},
|