good-eggs-mcp-server 0.1.0 → 0.1.2

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/build/index.js CHANGED
@@ -70,7 +70,7 @@ async function main() {
70
70
  // Step 1: Validate environment variables
71
71
  validateEnvironment();
72
72
  // Step 2: Create server using factory
73
- const { server, registerHandlers, cleanup } = createMCPServer();
73
+ const { server, registerHandlers, cleanup, startBackgroundLogin } = createMCPServer();
74
74
  // Step 3: Register all handlers (tools)
75
75
  await registerHandlers(server);
76
76
  // Step 4: Set up graceful shutdown
@@ -85,6 +85,23 @@ async function main() {
85
85
  const transport = new StdioServerTransport();
86
86
  await server.connect(transport);
87
87
  logServerStart('Good Eggs');
88
+ // Step 6: Start background login process
89
+ // This kicks off Playwright and performs login without blocking the stdio connection.
90
+ // If login fails, the server will close with an error.
91
+ logWarning('login', 'Starting background login to Good Eggs...');
92
+ startBackgroundLogin((error) => {
93
+ // Login failed - log error and exit
94
+ logError('login', `Background login failed: ${error.message}`);
95
+ logError('login', 'Server shutting down due to authentication failure.');
96
+ // Clean up and exit with error
97
+ cleanup()
98
+ .catch((cleanupError) => {
99
+ logError('cleanup', `Error during cleanup: ${cleanupError}`);
100
+ })
101
+ .finally(() => {
102
+ process.exit(1);
103
+ });
104
+ });
88
105
  }
89
106
  // Run the server
90
107
  main().catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "good-eggs-mcp-server",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "MCP server for Good Eggs grocery shopping with Playwright automation",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
@@ -90,6 +90,11 @@ export declare class GoodEggsClient implements IGoodEggsClient {
90
90
  getConfig(): GoodEggsConfig;
91
91
  }
92
92
  export type ClientFactory = () => IGoodEggsClient;
93
+ /**
94
+ * Callback invoked when background login fails
95
+ * @param error The error that caused login to fail
96
+ */
97
+ export type LoginFailedCallback = (error: Error) => void;
93
98
  export declare function createMCPServer(): {
94
99
  server: Server<{
95
100
  method: string;
@@ -116,5 +121,6 @@ export declare function createMCPServer(): {
116
121
  }>;
117
122
  registerHandlers: (server: Server, clientFactory?: ClientFactory) => Promise<void>;
118
123
  cleanup: () => Promise<void>;
124
+ startBackgroundLogin: (onFailed?: LoginFailedCallback) => void;
119
125
  };
120
126
  //# sourceMappingURL=server.d.ts.map
package/shared/server.js CHANGED
@@ -41,7 +41,9 @@ export class GoodEggsClient {
41
41
  });
42
42
  this.page = await this.context.newPage();
43
43
  // Navigate to login page
44
- await this.page.goto(`${BASE_URL}/signin`, { waitUntil: 'networkidle' });
44
+ // Use domcontentloaded instead of networkidle - Good Eggs has persistent connections that prevent networkidle
45
+ await this.page.goto(`${BASE_URL}/signin`, { waitUntil: 'domcontentloaded' });
46
+ await this.page.waitForTimeout(2000);
45
47
  // Fill in login credentials
46
48
  await this.page.fill('input[name="email"], input[type="email"]', this.config.username);
47
49
  await this.page.fill('input[name="password"], input[type="password"]', this.config.password);
@@ -61,22 +63,40 @@ export class GoodEggsClient {
61
63
  async searchGroceries(query) {
62
64
  const page = await this.ensureBrowser();
63
65
  // Navigate to search page
66
+ // Use domcontentloaded instead of networkidle - Good Eggs has persistent connections
64
67
  await page.goto(`${BASE_URL}/search?q=${encodeURIComponent(query)}`, {
65
- waitUntil: 'networkidle',
68
+ waitUntil: 'domcontentloaded',
66
69
  });
67
- // Wait for products to load
68
- await page.waitForTimeout(1000);
70
+ // Wait for React to render the product list
71
+ await page.waitForTimeout(3000);
69
72
  // Extract product information from the page
70
73
  const items = await page.evaluate(() => {
71
74
  const products = [];
72
- // Find all product cards/links by looking for product name elements
73
- const productElements = document.querySelectorAll('a[href*="/product/"], a[href*="goodeggs"]');
74
75
  const seen = new Set();
76
+ // Good Eggs uses 'js-product-link' class for product links
77
+ // URL format: /producer-slug/product-slug/product-id (e.g., /cloversfbay/organic-whole-milk/53fe295358ed090200000f2d)
78
+ const productElements = document.querySelectorAll('a.js-product-link');
75
79
  productElements.forEach((el) => {
76
80
  const link = el;
77
81
  const href = link.href;
78
- // Skip if not a product link or already seen
79
- if (!href || seen.has(href) || href.includes('/search') || href.includes('/signin')) {
82
+ // Skip if already seen or not a valid product URL
83
+ if (!href || seen.has(href)) {
84
+ return;
85
+ }
86
+ // Validate URL structure: should have at least 3 path segments after domain
87
+ const urlPath = new URL(href).pathname;
88
+ const segments = urlPath.split('/').filter((s) => s.length > 0);
89
+ if (segments.length < 3) {
90
+ return;
91
+ }
92
+ // Skip navigation pages
93
+ if (href.includes('/search') ||
94
+ href.includes('/signin') ||
95
+ href.includes('/home') ||
96
+ href.includes('/basket') ||
97
+ href.includes('/account') ||
98
+ href.includes('/favorites') ||
99
+ href.includes('/reorder')) {
80
100
  return;
81
101
  }
82
102
  // Try to find product info within or near this element
@@ -87,7 +107,11 @@ export class GoodEggsClient {
87
107
  const priceEl = container.querySelector('[class*="price"]');
88
108
  const discountEl = container.querySelector('[class*="off"], [class*="discount"]');
89
109
  const imgEl = container.querySelector('img');
90
- const name = nameEl?.textContent?.trim();
110
+ // Get name from element or from the link text itself
111
+ let name = nameEl?.textContent?.trim();
112
+ if (!name || name.length < 3) {
113
+ name = link.textContent?.trim();
114
+ }
91
115
  if (!name || name.length < 3)
92
116
  return;
93
117
  seen.add(href);
@@ -107,9 +131,10 @@ export class GoodEggsClient {
107
131
  async getFavorites() {
108
132
  const page = await this.ensureBrowser();
109
133
  // Navigate to favorites page
110
- await page.goto(`${BASE_URL}/favorites`, { waitUntil: 'networkidle' });
111
- // Wait for page to load
112
- await page.waitForTimeout(1000);
134
+ // Use domcontentloaded instead of networkidle - Good Eggs has persistent connections
135
+ await page.goto(`${BASE_URL}/favorites`, { waitUntil: 'domcontentloaded' });
136
+ // Wait for React to render
137
+ await page.waitForTimeout(3000);
113
138
  // Check if we're redirected to signin
114
139
  if (page.url().includes('/signin')) {
115
140
  throw new Error('Not logged in. Cannot access favorites.');
@@ -118,19 +143,29 @@ export class GoodEggsClient {
118
143
  const items = await page.evaluate(() => {
119
144
  const products = [];
120
145
  const seen = new Set();
121
- const productElements = document.querySelectorAll('a[href*="/product/"], a[href*="goodeggs"]');
146
+ // Good Eggs uses 'js-product-link' class for product links
147
+ const productElements = document.querySelectorAll('a.js-product-link');
122
148
  productElements.forEach((el) => {
123
149
  const link = el;
124
150
  const href = link.href;
125
151
  if (!href || seen.has(href) || href.includes('/favorites') || href.includes('/signin')) {
126
152
  return;
127
153
  }
154
+ // Validate URL structure
155
+ const urlPath = new URL(href).pathname;
156
+ const segments = urlPath.split('/').filter((s) => s.length > 0);
157
+ if (segments.length < 3) {
158
+ return;
159
+ }
128
160
  const container = link.closest('div[class*="product"], article, [class*="card"]') || link;
129
161
  const nameEl = container.querySelector('h2, h3, [class*="title"], [class*="name"]');
130
162
  const brandEl = container.querySelector('[class*="brand"], [class*="producer"]');
131
163
  const priceEl = container.querySelector('[class*="price"]');
132
164
  const imgEl = container.querySelector('img');
133
- const name = nameEl?.textContent?.trim();
165
+ let name = nameEl?.textContent?.trim();
166
+ if (!name || name.length < 3) {
167
+ name = link.textContent?.trim();
168
+ }
134
169
  if (!name || name.length < 3)
135
170
  return;
136
171
  seen.add(href);
@@ -152,11 +187,12 @@ export class GoodEggsClient {
152
187
  const currentUrl = page.url();
153
188
  if (!currentUrl.includes(groceryUrl) && !groceryUrl.includes(currentUrl)) {
154
189
  // Navigate to the product page
190
+ // Use domcontentloaded instead of networkidle - Good Eggs has persistent connections
155
191
  const fullUrl = groceryUrl.startsWith('http') ? groceryUrl : `${BASE_URL}${groceryUrl}`;
156
- await page.goto(fullUrl, { waitUntil: 'networkidle' });
192
+ await page.goto(fullUrl, { waitUntil: 'domcontentloaded' });
157
193
  }
158
- // Wait for page content to load
159
- await page.waitForTimeout(1000);
194
+ // Wait for React to render
195
+ await page.waitForTimeout(3000);
160
196
  // Extract product details
161
197
  const details = await page.evaluate((url) => {
162
198
  // Find the main product info
@@ -199,8 +235,10 @@ export class GoodEggsClient {
199
235
  const normalizedCurrentUrl = currentUrl.replace(BASE_URL, '');
200
236
  if (!normalizedCurrentUrl.includes(normalizedGroceryUrl.split('/').pop() || '')) {
201
237
  // Navigate to the product page
238
+ // Use domcontentloaded instead of networkidle - Good Eggs has persistent connections
202
239
  const fullUrl = groceryUrl.startsWith('http') ? groceryUrl : `${BASE_URL}${groceryUrl}`;
203
- await page.goto(fullUrl, { waitUntil: 'networkidle' });
240
+ await page.goto(fullUrl, { waitUntil: 'domcontentloaded' });
241
+ await page.waitForTimeout(3000);
204
242
  }
205
243
  // Get the product name for the result
206
244
  const itemName = await page.evaluate(() => {
@@ -265,8 +303,8 @@ export class GoodEggsClient {
265
303
  return await page.evaluate(() => {
266
304
  const products = [];
267
305
  const seenUrls = new Set();
268
- // Find all product links
269
- const productElements = document.querySelectorAll('a[href*="/product/"], a[href*="goodeggs"]');
306
+ // Good Eggs uses 'js-product-link' class for product links
307
+ const productElements = document.querySelectorAll('a.js-product-link');
270
308
  productElements.forEach((el) => {
271
309
  const link = el;
272
310
  const href = link.href;
@@ -276,6 +314,12 @@ export class GoodEggsClient {
276
314
  href.includes('/signin')) {
277
315
  return;
278
316
  }
317
+ // Validate URL structure
318
+ const urlPath = new URL(href).pathname;
319
+ const segments = urlPath.split('/').filter((s) => s.length > 0);
320
+ if (segments.length < 3) {
321
+ return;
322
+ }
279
323
  const container = link.closest('div[class*="product"], article, [class*="card"]') || link;
280
324
  // Look for price element and check if it's $0.00
281
325
  const priceEl = container.querySelector('[class*="price"]');
@@ -289,7 +333,10 @@ export class GoodEggsClient {
289
333
  const nameEl = container.querySelector('h2, h3, [class*="title"], [class*="name"]');
290
334
  const brandEl = container.querySelector('[class*="brand"], [class*="producer"]');
291
335
  const imgEl = container.querySelector('img');
292
- const name = nameEl?.textContent?.trim();
336
+ let name = nameEl?.textContent?.trim();
337
+ if (!name || name.length < 3) {
338
+ name = link.textContent?.trim();
339
+ }
293
340
  if (!name || name.length < 3)
294
341
  return;
295
342
  seenUrls.add(href);
@@ -306,8 +353,9 @@ export class GoodEggsClient {
306
353
  });
307
354
  };
308
355
  // Check homepage for free items
309
- await page.goto(BASE_URL, { waitUntil: 'networkidle' });
310
- await page.waitForTimeout(1000);
356
+ // Use domcontentloaded instead of networkidle - Good Eggs has persistent connections
357
+ await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
358
+ await page.waitForTimeout(3000);
311
359
  const homePageItems = await extractFreeItems();
312
360
  for (const item of homePageItems) {
313
361
  if (!seen.has(item.url)) {
@@ -316,8 +364,8 @@ export class GoodEggsClient {
316
364
  }
317
365
  }
318
366
  // Check /fresh-picks page for free items
319
- await page.goto(`${BASE_URL}/fresh-picks`, { waitUntil: 'networkidle' });
320
- await page.waitForTimeout(1000);
367
+ await page.goto(`${BASE_URL}/fresh-picks`, { waitUntil: 'domcontentloaded' });
368
+ await page.waitForTimeout(3000);
321
369
  const freshPicksItems = await extractFreeItems();
322
370
  for (const item of freshPicksItems) {
323
371
  if (!seen.has(item.url)) {
@@ -330,9 +378,10 @@ export class GoodEggsClient {
330
378
  async getPastOrderDates() {
331
379
  const page = await this.ensureBrowser();
332
380
  // Navigate to reorder/past orders page
333
- await page.goto(`${BASE_URL}/reorder`, { waitUntil: 'networkidle' });
334
- // Wait for page to load
335
- await page.waitForTimeout(1000);
381
+ // Use domcontentloaded instead of networkidle - Good Eggs has persistent connections
382
+ await page.goto(`${BASE_URL}/reorder`, { waitUntil: 'domcontentloaded' });
383
+ // Wait for React to render
384
+ await page.waitForTimeout(3000);
336
385
  // Check if we're redirected to signin
337
386
  if (page.url().includes('/signin')) {
338
387
  throw new Error('Not logged in. Cannot access past orders.');
@@ -363,8 +412,9 @@ export class GoodEggsClient {
363
412
  const page = await this.ensureBrowser();
364
413
  // First, go to reorder page
365
414
  if (!page.url().includes('/reorder')) {
366
- await page.goto(`${BASE_URL}/reorder`, { waitUntil: 'networkidle' });
367
- await page.waitForTimeout(1000);
415
+ // Use domcontentloaded instead of networkidle - Good Eggs has persistent connections
416
+ await page.goto(`${BASE_URL}/reorder`, { waitUntil: 'domcontentloaded' });
417
+ await page.waitForTimeout(3000);
368
418
  }
369
419
  // Check if we're redirected to signin
370
420
  if (page.url().includes('/signin')) {
@@ -374,25 +424,35 @@ export class GoodEggsClient {
374
424
  const orderLink = await page.$(`text=${orderDate}`);
375
425
  if (orderLink) {
376
426
  await orderLink.click();
377
- await page.waitForTimeout(1000);
427
+ await page.waitForTimeout(2000);
378
428
  }
379
429
  // Extract items from the order
380
430
  const items = await page.evaluate(() => {
381
431
  const products = [];
382
432
  const seen = new Set();
383
- const productElements = document.querySelectorAll('a[href*="/product/"], a[href*="goodeggs"]');
433
+ // Good Eggs uses 'js-product-link' class for product links
434
+ const productElements = document.querySelectorAll('a.js-product-link');
384
435
  productElements.forEach((el) => {
385
436
  const link = el;
386
437
  const href = link.href;
387
438
  if (!href || seen.has(href) || href.includes('/reorder') || href.includes('/signin')) {
388
439
  return;
389
440
  }
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;
446
+ }
390
447
  const container = link.closest('div[class*="product"], article, [class*="card"]') || link;
391
448
  const nameEl = container.querySelector('h2, h3, [class*="title"], [class*="name"]');
392
449
  const brandEl = container.querySelector('[class*="brand"], [class*="producer"]');
393
450
  const priceEl = container.querySelector('[class*="price"]');
394
451
  const imgEl = container.querySelector('img');
395
- const name = nameEl?.textContent?.trim();
452
+ let name = nameEl?.textContent?.trim();
453
+ if (!name || name.length < 3) {
454
+ name = link.textContent?.trim();
455
+ }
396
456
  if (!name || name.length < 3)
397
457
  return;
398
458
  seen.add(href);
@@ -415,8 +475,10 @@ export class GoodEggsClient {
415
475
  const normalizedGroceryUrl = groceryUrl.replace(BASE_URL, '');
416
476
  const normalizedCurrentUrl = currentUrl.replace(BASE_URL, '');
417
477
  if (!normalizedCurrentUrl.includes(normalizedGroceryUrl.split('/').pop() || '')) {
478
+ // Use domcontentloaded instead of networkidle - Good Eggs has persistent connections
418
479
  const fullUrl = groceryUrl.startsWith('http') ? groceryUrl : `${BASE_URL}${groceryUrl}`;
419
- await page.goto(fullUrl, { waitUntil: 'networkidle' });
480
+ await page.goto(fullUrl, { waitUntil: 'domcontentloaded' });
481
+ await page.waitForTimeout(3000);
420
482
  }
421
483
  // Get the product name for the result
422
484
  const itemName = await page.evaluate(() => {
@@ -463,8 +525,10 @@ export class GoodEggsClient {
463
525
  const normalizedGroceryUrl = groceryUrl.replace(BASE_URL, '');
464
526
  const normalizedCurrentUrl = currentUrl.replace(BASE_URL, '');
465
527
  if (!normalizedCurrentUrl.includes(normalizedGroceryUrl.split('/').pop() || '')) {
528
+ // Use domcontentloaded instead of networkidle - Good Eggs has persistent connections
466
529
  const fullUrl = groceryUrl.startsWith('http') ? groceryUrl : `${BASE_URL}${groceryUrl}`;
467
- await page.goto(fullUrl, { waitUntil: 'networkidle' });
530
+ await page.goto(fullUrl, { waitUntil: 'domcontentloaded' });
531
+ await page.waitForTimeout(3000);
468
532
  }
469
533
  // Get the product name for the result
470
534
  const itemName = await page.evaluate(() => {
@@ -507,8 +571,9 @@ export class GoodEggsClient {
507
571
  async removeFromCart(groceryUrl) {
508
572
  const page = await this.ensureBrowser();
509
573
  // Navigate to cart page
510
- await page.goto(`${BASE_URL}/basket`, { waitUntil: 'networkidle' });
511
- await page.waitForTimeout(1000);
574
+ // Use domcontentloaded instead of networkidle - Good Eggs has persistent connections
575
+ await page.goto(`${BASE_URL}/basket`, { waitUntil: 'domcontentloaded' });
576
+ await page.waitForTimeout(3000);
512
577
  // Try to find the item in the cart by its URL or name
513
578
  // First, let's get the product name from the URL if possible
514
579
  const productSlug = groceryUrl.split('/').pop() || '';
@@ -588,26 +653,86 @@ export function createMCPServer() {
588
653
  });
589
654
  // Track active client for cleanup
590
655
  let activeClient = null;
656
+ // Track background login state
657
+ let loginPromise = null;
658
+ let loginFailed = false;
659
+ let loginError = null;
660
+ let onLoginFailed = null;
661
+ /**
662
+ * Create the client instance (but don't initialize/login yet)
663
+ */
664
+ const createClient = () => {
665
+ const username = process.env.GOOD_EGGS_USERNAME;
666
+ const password = process.env.GOOD_EGGS_PASSWORD;
667
+ const headless = process.env.HEADLESS !== 'false';
668
+ const timeout = parseInt(process.env.TIMEOUT || '30000', 10);
669
+ if (!username || !password) {
670
+ throw new Error('GOOD_EGGS_USERNAME and GOOD_EGGS_PASSWORD environment variables must be configured');
671
+ }
672
+ activeClient = new GoodEggsClient({
673
+ username,
674
+ password,
675
+ headless,
676
+ timeout,
677
+ });
678
+ return activeClient;
679
+ };
680
+ /**
681
+ * Start background login process
682
+ * This should be called after the server is connected to start authentication
683
+ * without blocking the stdio connection.
684
+ *
685
+ * @param onFailed Callback invoked if login fails - use this to close the server
686
+ */
687
+ const startBackgroundLogin = (onFailed) => {
688
+ if (loginPromise) {
689
+ // Already started
690
+ return;
691
+ }
692
+ onLoginFailed = onFailed || null;
693
+ // Create client if not already created
694
+ if (!activeClient) {
695
+ createClient();
696
+ }
697
+ // Start login in background
698
+ loginPromise = activeClient.initialize().catch((error) => {
699
+ loginFailed = true;
700
+ loginError = error instanceof Error ? error : new Error(String(error));
701
+ // Invoke callback to notify about login failure
702
+ if (onLoginFailed) {
703
+ onLoginFailed(loginError);
704
+ }
705
+ // Re-throw to make the promise rejected
706
+ throw loginError;
707
+ });
708
+ };
709
+ /**
710
+ * Get a client that is ready to use (login completed)
711
+ * If background login was started, this waits for it to complete.
712
+ * If not started, this will initialize synchronously (blocking).
713
+ */
714
+ const getReadyClient = async () => {
715
+ // If login already failed, throw immediately
716
+ if (loginFailed && loginError) {
717
+ throw new Error(`Login failed: ${loginError.message}`);
718
+ }
719
+ // If background login is in progress, wait for it
720
+ if (loginPromise) {
721
+ await loginPromise;
722
+ return activeClient;
723
+ }
724
+ // No background login started - create and initialize client now (legacy behavior)
725
+ if (!activeClient) {
726
+ createClient();
727
+ }
728
+ await activeClient.initialize();
729
+ return activeClient;
730
+ };
591
731
  const registerHandlers = async (server, clientFactory) => {
592
- // Use provided factory or create default client
593
- const factory = clientFactory ||
594
- (() => {
595
- const username = process.env.GOOD_EGGS_USERNAME;
596
- const password = process.env.GOOD_EGGS_PASSWORD;
597
- const headless = process.env.HEADLESS !== 'false';
598
- const timeout = parseInt(process.env.TIMEOUT || '30000', 10);
599
- if (!username || !password) {
600
- throw new Error('GOOD_EGGS_USERNAME and GOOD_EGGS_PASSWORD environment variables must be configured');
601
- }
602
- activeClient = new GoodEggsClient({
603
- username,
604
- password,
605
- headless,
606
- timeout,
607
- });
608
- return activeClient;
609
- });
610
- const registerTools = createRegisterTools(factory);
732
+ // Use provided factory or create our managed client getter
733
+ const factory = clientFactory || (() => activeClient || createClient());
734
+ // Create tools with a special async getter that waits for background login
735
+ const registerTools = createRegisterTools(factory, getReadyClient);
611
736
  registerTools(server);
612
737
  };
613
738
  const cleanup = async () => {
@@ -615,6 +740,10 @@ export function createMCPServer() {
615
740
  await activeClient.close();
616
741
  activeClient = null;
617
742
  }
743
+ // Reset login state
744
+ loginPromise = null;
745
+ loginFailed = false;
746
+ loginError = null;
618
747
  };
619
- return { server, registerHandlers, cleanup };
748
+ return { server, registerHandlers, cleanup, startBackgroundLogin };
620
749
  }
package/shared/tools.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import { ClientFactory } from './server.js';
3
- export declare function createRegisterTools(clientFactory: ClientFactory): (server: Server) => void;
2
+ import { ClientFactory, IGoodEggsClient } from './server.js';
3
+ /**
4
+ * Async getter that returns a ready-to-use client (login completed)
5
+ */
6
+ export type GetReadyClientFn = () => Promise<IGoodEggsClient>;
7
+ export declare function createRegisterTools(clientFactory: ClientFactory, getReadyClient?: GetReadyClientFn): (server: Server) => void;
4
8
  //# sourceMappingURL=tools.d.ts.map
package/shared/tools.js CHANGED
@@ -86,11 +86,18 @@ Provide the Good Eggs URL of the item to remove from your cart.
86
86
  Navigates to the cart and removes the specified item.
87
87
 
88
88
  Returns confirmation of the removal or an error if the item is not in the cart.`;
89
- export function createRegisterTools(clientFactory) {
89
+ export function createRegisterTools(clientFactory, getReadyClient) {
90
90
  // Create a single client instance that persists across calls
91
91
  let client = null;
92
92
  let isInitialized = false;
93
+ // If getReadyClient is provided (background login mode), use it
94
+ // Otherwise fall back to lazy initialization (legacy behavior)
93
95
  const getClient = async () => {
96
+ if (getReadyClient) {
97
+ // Use the provided getter that handles background login
98
+ return getReadyClient();
99
+ }
100
+ // Legacy behavior: lazy initialization
94
101
  if (!client) {
95
102
  client = clientFactory();
96
103
  }