continentept-mcp 0.2.0 → 0.3.1

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/CHANGELOG.md CHANGED
@@ -6,6 +6,19 @@ All notable changes to this project are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.1] - 2026-10-15
10
+
11
+ ### Added
12
+
13
+ - Listed in the official [MCP Registry](https://registry.modelcontextprotocol.io) as `io.github.andredelgado/continente-mcp`, with both the npm package and the Claude Desktop extension. Every release updates the listing.
14
+
15
+ ## [0.3.0] - 2026-10-15
16
+
17
+ ### Added
18
+
19
+ - One-click Claude Desktop extension (`continente-mcp.mcpb`) attached to every release. It asks for your email and password at install time.
20
+ - Chromium downloads automatically the first time it is needed; `install-browser` is now optional. While it downloads, tools return a retryable `browser_unavailable` error explaining the wait.
21
+
9
22
  ## [0.2.0] - 2026-10-15
10
23
 
11
24
  ### Added
@@ -28,6 +41,8 @@ All notable changes to this project are documented here. The format follows
28
41
  - Automatic sign-in from environment variables or a private credentials file, and a `login` command.
29
42
  - The `basket-planner` skill for planning a shopping list and updating the basket.
30
43
 
31
- [Unreleased]: https://github.com/andredelgado/continente-mcp/compare/v0.2.0...HEAD
44
+ [Unreleased]: https://github.com/andredelgado/continente-mcp/compare/v0.3.1...HEAD
45
+ [0.3.1]: https://github.com/andredelgado/continente-mcp/compare/v0.3.0...v0.3.1
46
+ [0.3.0]: https://github.com/andredelgado/continente-mcp/compare/v0.2.0...v0.3.0
32
47
  [0.2.0]: https://github.com/andredelgado/continente-mcp/compare/v0.1.0...v0.2.0
33
48
  [0.1.0]: https://github.com/andredelgado/continente-mcp/releases/tag/v0.1.0
package/README.md CHANGED
@@ -29,20 +29,29 @@ on the website.
29
29
 
30
30
  ## Requirements
31
31
 
32
- - Node.js 22 or newer
33
32
  - A Continente.pt account for account features (search and product details work without one)
33
+ - Claude Desktop on macOS or Windows for the one-click install, or Node.js 22 or newer for
34
+ any other MCP client
34
35
 
35
- ## Quick start
36
+ The server drives the website with Chromium, which it downloads automatically the first time
37
+ it needs it (about 150 MB, once).
36
38
 
37
- **1. Install the browser** the server uses to talk to the website (one-time download):
39
+ ## Install in Claude Desktop (one click)
38
40
 
39
- ```sh
40
- npx -y continentept-mcp install-browser
41
- ```
41
+ 1. Download [**continente-mcp.mcpb**](https://github.com/andredelgado/continente-mcp/releases/latest/download/continente-mcp.mcpb)
42
+ from the latest release.
43
+ 2. Open it. Claude Desktop shows an install dialog; click **Install**.
44
+ 3. Enter your Continente email and password when asked. Claude Desktop keeps the password
45
+ in its secure settings storage.
42
46
 
43
- On Linux, append `--with-deps` to also install the required system libraries.
47
+ That's it. The extension updates when you install a newer `.mcpb`. The server is also listed
48
+ in the [MCP Registry](https://registry.modelcontextprotocol.io) as
49
+ `io.github.andredelgado/continente-mcp`, so clients that browse the registry can find it. To use 1Password instead
50
+ of storing the password in Claude Desktop, use the manual setup below.
44
51
 
45
- **2. Save your credentials** in `~/.continentept-mcp/credentials.env` (see
52
+ ## Manual setup (any MCP client)
53
+
54
+ **1. Save your credentials** in `~/.continentept-mcp/credentials.env` (see
46
55
  [configuration](docs/configuration.md) for other options):
47
56
 
48
57
  ```sh
@@ -60,7 +69,7 @@ Optionally check that login works:
60
69
  npx -y continentept-mcp login
61
70
  ```
62
71
 
63
- **3. Add the server to your MCP client.**
72
+ **2. Add the server to your MCP client.**
64
73
 
65
74
  Claude Code:
66
75
 
@@ -84,6 +93,10 @@ Claude Desktop, Cursor and other clients (`claude_desktop_config.json`, `mcp.jso
84
93
  To keep the credentials file elsewhere, add
85
94
  `"env": { "CONTINENTE_CREDENTIALS_FILE": "/absolute/path/to/credentials.env" }`.
86
95
 
96
+ Chromium downloads itself on first use. To fetch it ahead of time, run
97
+ `npx -y continentept-mcp install-browser` (on Linux, add `--with-deps` for the system
98
+ libraries it needs).
99
+
87
100
  Then ask your assistant something like *"Add semi-skimmed milk and a dozen eggs to my
88
101
  Continente cart"* or *"What do I usually buy? Build me a list for this week."*
89
102
 
@@ -129,7 +142,7 @@ then apply it automatically whenever you ask about groceries.
129
142
  ```text
130
143
  continentept-mcp Start the MCP server on stdio
131
144
  continentept-mcp login Sign in and save the session (--show-browser to watch)
132
- continentept-mcp install-browser Download Chromium (extra arguments go to Playwright)
145
+ continentept-mcp install-browser Download Chromium now instead of on first use (extra arguments go to Playwright)
133
146
  continentept-mcp --version
134
147
  ```
135
148
 
package/dist/cli.js CHANGED
@@ -1,12 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from 'node:fs';
3
- import { createRequire } from 'node:module';
4
- import { dirname, join } from 'node:path';
5
- import { spawnSync } from 'node:child_process';
6
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
4
  import { createShoppingApplication } from './runtime.js';
8
5
  import { runAutoLogin } from './auto-login.js';
9
6
  import { isMainModule } from './main-module.js';
7
+ import { runChromiumInstall } from './session/chromium.js';
10
8
  const USAGE = `Usage: continentept-mcp [command]
11
9
 
12
10
  Commands:
@@ -57,11 +55,6 @@ export async function connectStdioApplication(application) {
57
55
  throw error;
58
56
  }
59
57
  }
60
- function installBrowser(extra) {
61
- const playwright = dirname(createRequire(import.meta.url).resolve('playwright/package.json'));
62
- const result = spawnSync(process.execPath, [join(playwright, 'cli.js'), 'install', 'chromium', ...extra], { stdio: 'inherit' });
63
- return result.status ?? 1;
64
- }
65
58
  export async function main(args) {
66
59
  const [command, ...rest] = args;
67
60
  switch (command) {
@@ -78,7 +71,7 @@ export async function main(args) {
78
71
  return 1;
79
72
  }
80
73
  case 'install-browser':
81
- return installBrowser(rest);
74
+ return runChromiumInstall(rest);
82
75
  case '-v':
83
76
  case '--version':
84
77
  console.log(packageVersion());
@@ -7,7 +7,7 @@ export interface HttpResponse {
7
7
  }
8
8
  /** Only known authentication evidence triggers recovery; arbitrary upstream failures do not. */
9
9
  export declare function responseStatus(response: HttpResponse | null, finalUrl: string): import("../domain/result.js").Result<undefined, {
10
- code: "cache_unavailable" | "cancelled" | "cart_change_rejected" | "cart_change_unconfirmed" | "deadline_exceeded" | "invalid_data" | "invalid_input" | "not_authenticated" | "not_found" | "order_details_unavailable" | "quantity_below_minimum" | "queue_full" | "sale_rules_unknown" | "server_closed" | "upstream_unavailable";
10
+ code: "browser_unavailable" | "cache_unavailable" | "cancelled" | "cart_change_rejected" | "cart_change_unconfirmed" | "deadline_exceeded" | "invalid_data" | "invalid_input" | "not_authenticated" | "not_found" | "order_details_unavailable" | "quantity_below_minimum" | "queue_full" | "sale_rules_unknown" | "server_closed" | "upstream_unavailable";
11
11
  message: string;
12
12
  retryable: boolean;
13
13
  issues?: {
@@ -16,7 +16,7 @@ export declare function responseStatus(response: HttpResponse | null, finalUrl:
16
16
  }[] | undefined;
17
17
  }>;
18
18
  export declare function authenticatedPayload(input: unknown): import("../domain/result.js").Result<unknown, {
19
- code: "cache_unavailable" | "cancelled" | "cart_change_rejected" | "cart_change_unconfirmed" | "deadline_exceeded" | "invalid_data" | "invalid_input" | "not_authenticated" | "not_found" | "order_details_unavailable" | "quantity_below_minimum" | "queue_full" | "sale_rules_unknown" | "server_closed" | "upstream_unavailable";
19
+ code: "browser_unavailable" | "cache_unavailable" | "cancelled" | "cart_change_rejected" | "cart_change_unconfirmed" | "deadline_exceeded" | "invalid_data" | "invalid_input" | "not_authenticated" | "not_found" | "order_details_unavailable" | "quantity_below_minimum" | "queue_full" | "sale_rules_unknown" | "server_closed" | "upstream_unavailable";
20
20
  message: string;
21
21
  retryable: boolean;
22
22
  issues?: {
@@ -1,4 +1,5 @@
1
1
  import { requestIO, requestTimeout, checkRequest, RequestStopped, reportProgress } from '../session/request.js';
2
+ import { BrowserUnavailable } from '../session/chromium.js';
2
3
  import { failure, success } from '../domain/index.js';
3
4
  import { parseProductSearchPage, parseSearchPagination, parseFavoritesPageData, STOREFRONT_ORIGIN, storefrontUrl } from '../parsers/catalog.js';
4
5
  import { responseStatus, authenticatedPayload } from './policy.js';
@@ -6,6 +7,12 @@ import { orderHistoryUrl } from '../parsers/orders.js';
6
7
  import { field } from '../parsers/records.js';
7
8
  export const CART_SUMMARY_URL = `${STOREFRONT_ORIGIN}/on/demandware.store/Sites-continente-Site/default/Cart-MiniCartShow`;
8
9
  const ORDER_DETAIL_PATHS = ['/conta/detalhe-encomenda/', '/detalhe-encomenda'];
10
+ /** Map an error from starting or using the browser; cancellation keeps propagating. */
11
+ function browserFailure(error) {
12
+ if (error instanceof RequestStopped)
13
+ throw error;
14
+ return error instanceof BrowserUnavailable ? { ok: false, error: error.toolError } : failure('upstream_unavailable');
15
+ }
9
16
  const productPath = (reference) => `/produto/${reference.endsWith('.html') ? reference : `${reference}.html`}`;
10
17
  export class StorefrontClient {
11
18
  session;
@@ -30,9 +37,7 @@ export class StorefrontClient {
30
37
  return success(lease);
31
38
  }
32
39
  catch (error) {
33
- if (error instanceof RequestStopped)
34
- throw error;
35
- return failure('upstream_unavailable');
40
+ return browserFailure(error);
36
41
  }
37
42
  }
38
43
  async html(path, marker = 'main, [role="main"], .product-line, a[href*="/produto/"]') {
@@ -125,7 +130,7 @@ async function readContinuation(site, url) {
125
130
  return status.ok ? success(await site.io(lease, () => response.text())) : status;
126
131
  }
127
132
  catch (error) {
128
- return failure(error instanceof RequestStopped ? error.code : 'upstream_unavailable');
133
+ return error instanceof RequestStopped ? failure(error.code) : browserFailure(error);
129
134
  }
130
135
  }
131
136
  export class FavoritesClient {
@@ -196,9 +201,7 @@ export class CartClient {
196
201
  return authenticatedPayload(data);
197
202
  }
198
203
  catch (error) {
199
- if (error instanceof RequestStopped)
200
- throw error;
201
- return failure('upstream_unavailable');
204
+ return browserFailure(error);
202
205
  }
203
206
  }
204
207
  async fetchProductForm(reference) {
@@ -264,9 +267,7 @@ export class CartClient {
264
267
  });
265
268
  }
266
269
  catch (error) {
267
- if (error instanceof RequestStopped)
268
- throw error;
269
- return failure('upstream_unavailable');
270
+ return browserFailure(error);
270
271
  }
271
272
  }
272
273
  }
@@ -288,9 +289,7 @@ export class OrdersClient {
288
289
  return success(await this.site.io(response.value, () => page.content()));
289
290
  }
290
291
  catch (error) {
291
- if (error instanceof RequestStopped)
292
- throw error;
293
- return failure('upstream_unavailable');
292
+ return browserFailure(error);
294
293
  }
295
294
  }
296
295
  async fetchOrderLines(url) {
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  export declare const ToolErrorCodeSchema: z.ZodEnum<{
3
+ browser_unavailable: "browser_unavailable";
3
4
  cache_unavailable: "cache_unavailable";
4
5
  cancelled: "cancelled";
5
6
  cart_change_rejected: "cart_change_rejected";
@@ -18,6 +19,7 @@ export declare const ToolErrorCodeSchema: z.ZodEnum<{
18
19
  }>;
19
20
  export declare const ToolErrorSchema: z.ZodObject<{
20
21
  code: z.ZodEnum<{
22
+ browser_unavailable: "browser_unavailable";
21
23
  cache_unavailable: "cache_unavailable";
22
24
  cancelled: "cancelled";
23
25
  cart_change_rejected: "cart_change_rejected";
@@ -4,7 +4,7 @@ export const ToolErrorCodeSchema = z.enum([
4
4
  'not_found', 'sale_rules_unknown', 'quantity_below_minimum',
5
5
  'cart_change_rejected', 'cart_change_unconfirmed', 'cache_unavailable',
6
6
  'order_details_unavailable',
7
- 'cancelled', 'deadline_exceeded', 'server_closed', 'queue_full',
7
+ 'cancelled', 'deadline_exceeded', 'server_closed', 'queue_full', 'browser_unavailable',
8
8
  ]);
9
9
  export const ToolErrorSchema = z.strictObject({
10
10
  code: ToolErrorCodeSchema,
@@ -176,6 +176,7 @@ export declare const OrderSchema: z.ZodObject<{
176
176
  status: z.ZodLiteral<"unavailable">;
177
177
  error: z.ZodObject<{
178
178
  code: z.ZodEnum<{
179
+ browser_unavailable: "browser_unavailable";
179
180
  cache_unavailable: "cache_unavailable";
180
181
  cancelled: "cancelled";
181
182
  cart_change_rejected: "cart_change_rejected";
@@ -317,6 +318,7 @@ export declare const ProductDetailsSchema: z.ZodObject<{
317
318
  status: z.ZodLiteral<"unavailable">;
318
319
  error: z.ZodObject<{
319
320
  code: z.ZodEnum<{
321
+ browser_unavailable: "browser_unavailable";
320
322
  cache_unavailable: "cache_unavailable";
321
323
  cancelled: "cancelled";
322
324
  cart_change_rejected: "cart_change_rejected";
@@ -42,6 +42,7 @@ export declare const CartMutationResultSchema: z.ZodDiscriminatedUnion<[z.ZodObj
42
42
  outcome: z.ZodLiteral<"not_applied">;
43
43
  error: z.ZodObject<{
44
44
  code: z.ZodEnum<{
45
+ browser_unavailable: "browser_unavailable";
45
46
  cache_unavailable: "cache_unavailable";
46
47
  cancelled: "cancelled";
47
48
  cart_change_rejected: "cart_change_rejected";
@@ -80,6 +81,7 @@ export declare const CartMutationResultSchema: z.ZodDiscriminatedUnion<[z.ZodObj
80
81
  outcome: z.ZodLiteral<"unknown">;
81
82
  error: z.ZodObject<{
82
83
  code: z.ZodEnum<{
84
+ browser_unavailable: "browser_unavailable";
83
85
  cache_unavailable: "cache_unavailable";
84
86
  cancelled: "cancelled";
85
87
  cart_change_rejected: "cart_change_rejected";
@@ -119,6 +121,7 @@ export declare function pageResultSchema<T extends z.ZodType>(item: T): z.ZodObj
119
121
  }>;
120
122
  failures: z.ZodArray<z.ZodObject<{
121
123
  code: z.ZodEnum<{
124
+ browser_unavailable: "browser_unavailable";
122
125
  cache_unavailable: "cache_unavailable";
123
126
  cancelled: "cancelled";
124
127
  cart_change_rejected: "cart_change_rejected";
@@ -44,6 +44,7 @@ export declare const PublicErrorSchema: z.ZodObject<{
44
44
  code: z.ZodString;
45
45
  }, z.core.$strict>>>;
46
46
  code: z.ZodUnion<readonly [z.ZodEnum<{
47
+ browser_unavailable: "browser_unavailable";
47
48
  cache_unavailable: "cache_unavailable";
48
49
  cancelled: "cancelled";
49
50
  cart_change_rejected: "cart_change_rejected";
@@ -74,6 +75,7 @@ export declare const ErrorResultSchema: z.ZodObject<{
74
75
  code: z.ZodString;
75
76
  }, z.core.$strict>>>;
76
77
  code: z.ZodUnion<readonly [z.ZodEnum<{
78
+ browser_unavailable: "browser_unavailable";
77
79
  cache_unavailable: "cache_unavailable";
78
80
  cancelled: "cancelled";
79
81
  cart_change_rejected: "cart_change_rejected";
@@ -115,6 +117,7 @@ export declare const ExecutionErrorSchema: z.ZodObject<{
115
117
  code: z.ZodString;
116
118
  }, z.core.$strict>>>;
117
119
  code: z.ZodUnion<readonly [z.ZodEnum<{
120
+ browser_unavailable: "browser_unavailable";
118
121
  cache_unavailable: "cache_unavailable";
119
122
  cancelled: "cancelled";
120
123
  cart_change_rejected: "cart_change_rejected";
@@ -164,6 +167,7 @@ export declare function outputSchema<T extends z.ZodType>(data: T, extra?: z.Zod
164
167
  code: z.ZodString;
165
168
  }, z.core.$strict>>>;
166
169
  code: z.ZodUnion<readonly [z.ZodEnum<{
170
+ browser_unavailable: "browser_unavailable";
167
171
  cache_unavailable: "cache_unavailable";
168
172
  cancelled: "cancelled";
169
173
  cart_change_rejected: "cart_change_rejected";
@@ -220,6 +224,7 @@ export declare const SearchData: z.ZodObject<{
220
224
  }>;
221
225
  failures: z.ZodArray<z.ZodObject<{
222
226
  code: z.ZodEnum<{
227
+ browser_unavailable: "browser_unavailable";
223
228
  cache_unavailable: "cache_unavailable";
224
229
  cancelled: "cancelled";
225
230
  cart_change_rejected: "cart_change_rejected";
@@ -314,6 +319,7 @@ export declare const OrdersData: z.ZodObject<{
314
319
  status: z.ZodLiteral<"unavailable">;
315
320
  error: z.ZodObject<{
316
321
  code: z.ZodEnum<{
322
+ browser_unavailable: "browser_unavailable";
317
323
  cache_unavailable: "cache_unavailable";
318
324
  cancelled: "cancelled";
319
325
  cart_change_rejected: "cart_change_rejected";
@@ -348,6 +354,7 @@ export declare const OrdersData: z.ZodObject<{
348
354
  }>;
349
355
  failures: z.ZodArray<z.ZodObject<{
350
356
  code: z.ZodEnum<{
357
+ browser_unavailable: "browser_unavailable";
351
358
  cache_unavailable: "cache_unavailable";
352
359
  cancelled: "cancelled";
353
360
  cart_change_rejected: "cart_change_rejected";
@@ -384,6 +391,7 @@ export declare const OrdersData: z.ZodObject<{
384
391
  orderNumber: z.ZodNullable<z.core.$ZodBranded<z.ZodString, "OrderNumber", "out">>;
385
392
  error: z.ZodObject<{
386
393
  code: z.ZodEnum<{
394
+ browser_unavailable: "browser_unavailable";
387
395
  cache_unavailable: "cache_unavailable";
388
396
  cancelled: "cancelled";
389
397
  cart_change_rejected: "cart_change_rejected";
@@ -445,6 +453,7 @@ export declare const FrequencyData: z.ZodObject<{
445
453
  orderNumber: z.ZodNullable<z.core.$ZodBranded<z.ZodString, "OrderNumber", "out">>;
446
454
  error: z.ZodObject<{
447
455
  code: z.ZodEnum<{
456
+ browser_unavailable: "browser_unavailable";
448
457
  cache_unavailable: "cache_unavailable";
449
458
  cancelled: "cancelled";
450
459
  cart_change_rejected: "cart_change_rejected";
@@ -508,6 +517,7 @@ export declare const SearchOutput: z.ZodObject<{
508
517
  }>;
509
518
  failures: z.ZodArray<z.ZodObject<{
510
519
  code: z.ZodEnum<{
520
+ browser_unavailable: "browser_unavailable";
511
521
  cache_unavailable: "cache_unavailable";
512
522
  cancelled: "cancelled";
513
523
  cart_change_rejected: "cart_change_rejected";
@@ -543,6 +553,7 @@ export declare const SearchOutput: z.ZodObject<{
543
553
  code: z.ZodString;
544
554
  }, z.core.$strict>>>;
545
555
  code: z.ZodUnion<readonly [z.ZodEnum<{
556
+ browser_unavailable: "browser_unavailable";
546
557
  cache_unavailable: "cache_unavailable";
547
558
  cancelled: "cancelled";
548
559
  cart_change_rejected: "cart_change_rejected";
@@ -621,6 +632,7 @@ export declare const FavoritesOutput: z.ZodObject<{
621
632
  code: z.ZodString;
622
633
  }, z.core.$strict>>>;
623
634
  code: z.ZodUnion<readonly [z.ZodEnum<{
635
+ browser_unavailable: "browser_unavailable";
624
636
  cache_unavailable: "cache_unavailable";
625
637
  cancelled: "cancelled";
626
638
  cart_change_rejected: "cart_change_rejected";
@@ -715,6 +727,7 @@ export declare const CartOutput: z.ZodObject<{
715
727
  code: z.ZodString;
716
728
  }, z.core.$strict>>>;
717
729
  code: z.ZodUnion<readonly [z.ZodEnum<{
730
+ browser_unavailable: "browser_unavailable";
718
731
  cache_unavailable: "cache_unavailable";
719
732
  cancelled: "cancelled";
720
733
  cart_change_rejected: "cart_change_rejected";
@@ -792,6 +805,7 @@ export declare const MutationOutput: z.ZodObject<{
792
805
  code: z.ZodString;
793
806
  }, z.core.$strict>>>;
794
807
  code: z.ZodUnion<readonly [z.ZodEnum<{
808
+ browser_unavailable: "browser_unavailable";
795
809
  cache_unavailable: "cache_unavailable";
796
810
  cancelled: "cancelled";
797
811
  cart_change_rejected: "cart_change_rejected";
@@ -869,6 +883,7 @@ export declare const OrdersOutput: z.ZodObject<{
869
883
  status: z.ZodLiteral<"unavailable">;
870
884
  error: z.ZodObject<{
871
885
  code: z.ZodEnum<{
886
+ browser_unavailable: "browser_unavailable";
872
887
  cache_unavailable: "cache_unavailable";
873
888
  cancelled: "cancelled";
874
889
  cart_change_rejected: "cart_change_rejected";
@@ -903,6 +918,7 @@ export declare const OrdersOutput: z.ZodObject<{
903
918
  }>;
904
919
  failures: z.ZodArray<z.ZodObject<{
905
920
  code: z.ZodEnum<{
921
+ browser_unavailable: "browser_unavailable";
906
922
  cache_unavailable: "cache_unavailable";
907
923
  cancelled: "cancelled";
908
924
  cart_change_rejected: "cart_change_rejected";
@@ -939,6 +955,7 @@ export declare const OrdersOutput: z.ZodObject<{
939
955
  orderNumber: z.ZodNullable<z.core.$ZodBranded<z.ZodString, "OrderNumber", "out">>;
940
956
  error: z.ZodObject<{
941
957
  code: z.ZodEnum<{
958
+ browser_unavailable: "browser_unavailable";
942
959
  cache_unavailable: "cache_unavailable";
943
960
  cancelled: "cancelled";
944
961
  cart_change_rejected: "cart_change_rejected";
@@ -974,6 +991,7 @@ export declare const OrdersOutput: z.ZodObject<{
974
991
  code: z.ZodString;
975
992
  }, z.core.$strict>>>;
976
993
  code: z.ZodUnion<readonly [z.ZodEnum<{
994
+ browser_unavailable: "browser_unavailable";
977
995
  cache_unavailable: "cache_unavailable";
978
996
  cancelled: "cancelled";
979
997
  cart_change_rejected: "cart_change_rejected";
@@ -1051,6 +1069,7 @@ export declare const SingleOrderOutput: z.ZodObject<{
1051
1069
  status: z.ZodLiteral<"unavailable">;
1052
1070
  error: z.ZodObject<{
1053
1071
  code: z.ZodEnum<{
1072
+ browser_unavailable: "browser_unavailable";
1054
1073
  cache_unavailable: "cache_unavailable";
1055
1074
  cancelled: "cancelled";
1056
1075
  cart_change_rejected: "cart_change_rejected";
@@ -1090,6 +1109,7 @@ export declare const SingleOrderOutput: z.ZodObject<{
1090
1109
  code: z.ZodString;
1091
1110
  }, z.core.$strict>>>;
1092
1111
  code: z.ZodUnion<readonly [z.ZodEnum<{
1112
+ browser_unavailable: "browser_unavailable";
1093
1113
  cache_unavailable: "cache_unavailable";
1094
1114
  cancelled: "cancelled";
1095
1115
  cart_change_rejected: "cart_change_rejected";
@@ -1164,6 +1184,7 @@ export declare const FrequencyOutput: z.ZodObject<{
1164
1184
  orderNumber: z.ZodNullable<z.core.$ZodBranded<z.ZodString, "OrderNumber", "out">>;
1165
1185
  error: z.ZodObject<{
1166
1186
  code: z.ZodEnum<{
1187
+ browser_unavailable: "browser_unavailable";
1167
1188
  cache_unavailable: "cache_unavailable";
1168
1189
  cancelled: "cancelled";
1169
1190
  cart_change_rejected: "cart_change_rejected";
@@ -1205,6 +1226,7 @@ export declare const FrequencyOutput: z.ZodObject<{
1205
1226
  code: z.ZodString;
1206
1227
  }, z.core.$strict>>>;
1207
1228
  code: z.ZodUnion<readonly [z.ZodEnum<{
1229
+ browser_unavailable: "browser_unavailable";
1208
1230
  cache_unavailable: "cache_unavailable";
1209
1231
  cancelled: "cancelled";
1210
1232
  cart_change_rejected: "cart_change_rejected";
@@ -1290,6 +1312,7 @@ export declare const SessionStatusOutput: z.ZodObject<{
1290
1312
  code: z.ZodString;
1291
1313
  }, z.core.$strict>>>;
1292
1314
  code: z.ZodUnion<readonly [z.ZodEnum<{
1315
+ browser_unavailable: "browser_unavailable";
1293
1316
  cache_unavailable: "cache_unavailable";
1294
1317
  cancelled: "cancelled";
1295
1318
  cart_change_rejected: "cart_change_rejected";
@@ -1406,6 +1429,7 @@ export declare const ProductDetailsOutput: z.ZodObject<{
1406
1429
  status: z.ZodLiteral<"unavailable">;
1407
1430
  error: z.ZodObject<{
1408
1431
  code: z.ZodEnum<{
1432
+ browser_unavailable: "browser_unavailable";
1409
1433
  cache_unavailable: "cache_unavailable";
1410
1434
  cancelled: "cancelled";
1411
1435
  cart_change_rejected: "cart_change_rejected";
@@ -1441,6 +1465,7 @@ export declare const ProductDetailsOutput: z.ZodObject<{
1441
1465
  code: z.ZodString;
1442
1466
  }, z.core.$strict>>>;
1443
1467
  code: z.ZodUnion<readonly [z.ZodEnum<{
1468
+ browser_unavailable: "browser_unavailable";
1444
1469
  cache_unavailable: "cache_unavailable";
1445
1470
  cancelled: "cancelled";
1446
1471
  cart_change_rejected: "cart_change_rejected";
@@ -21,7 +21,7 @@ export declare function parseProductForm(input: unknown, expectedId: ProductId):
21
21
  csrf: string;
22
22
  sale: SaleMetadata;
23
23
  }, {
24
- code: "cache_unavailable" | "cancelled" | "cart_change_rejected" | "cart_change_unconfirmed" | "deadline_exceeded" | "invalid_data" | "invalid_input" | "not_authenticated" | "not_found" | "order_details_unavailable" | "quantity_below_minimum" | "queue_full" | "sale_rules_unknown" | "server_closed" | "upstream_unavailable";
24
+ code: "browser_unavailable" | "cache_unavailable" | "cancelled" | "cart_change_rejected" | "cart_change_unconfirmed" | "deadline_exceeded" | "invalid_data" | "invalid_input" | "not_authenticated" | "not_found" | "order_details_unavailable" | "quantity_below_minimum" | "queue_full" | "sale_rules_unknown" | "server_closed" | "upstream_unavailable";
25
25
  message: string;
26
26
  retryable: boolean;
27
27
  issues?: {
@@ -37,7 +37,7 @@ export declare function parseCartControls(html: string, productId: ProductId, ca
37
37
  measureOptions: string;
38
38
  gtmIndex: string;
39
39
  }, {
40
- code: "cache_unavailable" | "cancelled" | "cart_change_rejected" | "cart_change_unconfirmed" | "deadline_exceeded" | "invalid_data" | "invalid_input" | "not_authenticated" | "not_found" | "order_details_unavailable" | "quantity_below_minimum" | "queue_full" | "sale_rules_unknown" | "server_closed" | "upstream_unavailable";
40
+ code: "browser_unavailable" | "cache_unavailable" | "cancelled" | "cart_change_rejected" | "cart_change_unconfirmed" | "deadline_exceeded" | "invalid_data" | "invalid_input" | "not_authenticated" | "not_found" | "order_details_unavailable" | "quantity_below_minimum" | "queue_full" | "sale_rules_unknown" | "server_closed" | "upstream_unavailable";
41
41
  message: string;
42
42
  retryable: boolean;
43
43
  issues?: {
@@ -5,7 +5,7 @@ export declare function productReferenceFromUrl(href: string): Result<{
5
5
  productId: string & import("zod").$brand<"ProductId">;
6
6
  productSlug: (string & import("zod").$brand<"ProductSlug">) | null;
7
7
  }, {
8
- code: "cache_unavailable" | "cancelled" | "cart_change_rejected" | "cart_change_unconfirmed" | "deadline_exceeded" | "invalid_data" | "invalid_input" | "not_authenticated" | "not_found" | "order_details_unavailable" | "quantity_below_minimum" | "queue_full" | "sale_rules_unknown" | "server_closed" | "upstream_unavailable";
8
+ code: "browser_unavailable" | "cache_unavailable" | "cancelled" | "cart_change_rejected" | "cart_change_unconfirmed" | "deadline_exceeded" | "invalid_data" | "invalid_input" | "not_authenticated" | "not_found" | "order_details_unavailable" | "quantity_below_minimum" | "queue_full" | "sale_rules_unknown" | "server_closed" | "upstream_unavailable";
9
9
  message: string;
10
10
  retryable: boolean;
11
11
  issues?: {
package/dist/runtime.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { CookieStore } from './stores/files.js';
4
4
  import type { BrowserLauncher, StorefrontSession } from './session/browser.js';
5
5
  import type { ShoppingServices } from './mcp/shopping.js';
6
6
  export declare const systemClock: Clock;
7
+ /** Launch Playwright's Chromium, downloading it first if this machine does not have it yet. */
7
8
  export declare const launchBrowser: BrowserLauncher;
8
9
  export interface RuntimeOptions {
9
10
  env?: Environment;
package/dist/runtime.js CHANGED
@@ -2,6 +2,7 @@ import { createApplication } from './application.js';
2
2
  import { favoritesScope, loadSettings } from './config.js';
3
3
  import { FileCookieStore, FileFavoritesStore } from './stores/files.js';
4
4
  import { BrowserSession } from './session/browser.js';
5
+ import { ChromiumInstaller, runChromiumInstall } from './session/chromium.js';
5
6
  import { AuthenticationClient } from './clients/authentication.js';
6
7
  import { StorefrontClient, CatalogClient, FavoritesClient, CartClient, OrdersClient } from './clients/storefront.js';
7
8
  import { CatalogService, FavoritesService } from './services/catalog.js';
@@ -10,7 +11,15 @@ import { OrderService, PurchaseHistoryService } from './services/orders.js';
10
11
  import { registerShoppingTools } from './mcp/shopping.js';
11
12
  import { SERVER_INSTRUCTIONS, basketPlannerPrompt } from './mcp/guide.js';
12
13
  export const systemClock = { now: () => new Date() };
13
- export const launchBrowser = async (options) => (await import('playwright')).chromium.launch(options);
14
+ const chromiumInstaller = new ChromiumInstaller(async () => {
15
+ // Skip per-percent progress lines; keep the start, finish and any errors.
16
+ const code = await runChromiumInstall([], line => { if (!line.includes('%'))
17
+ console.error(`[chromium] ${line}`); });
18
+ if (code !== 0)
19
+ throw new Error(`Chromium download exited with code ${code}`);
20
+ }, message => console.error(message));
21
+ /** Launch Playwright's Chromium, downloading it first if this machine does not have it yet. */
22
+ export const launchBrowser = chromiumInstaller.launcher(async (options) => (await import('playwright')).chromium.launch(options));
14
23
  /** Build the shopping services. Nothing is read or launched until this is called. */
15
24
  export function createShoppingServices(options = {}) {
16
25
  const settings = loadSettings(options.env ?? process.env);
@@ -67,7 +67,7 @@ export declare class CatalogService {
67
67
  } | {
68
68
  status: "unavailable";
69
69
  error: {
70
- code: "cache_unavailable" | "cancelled" | "cart_change_rejected" | "cart_change_unconfirmed" | "deadline_exceeded" | "invalid_data" | "invalid_input" | "not_authenticated" | "not_found" | "order_details_unavailable" | "quantity_below_minimum" | "queue_full" | "sale_rules_unknown" | "server_closed" | "upstream_unavailable";
70
+ code: "browser_unavailable" | "cache_unavailable" | "cancelled" | "cart_change_rejected" | "cart_change_unconfirmed" | "deadline_exceeded" | "invalid_data" | "invalid_input" | "not_authenticated" | "not_found" | "order_details_unavailable" | "quantity_below_minimum" | "queue_full" | "sale_rules_unknown" | "server_closed" | "upstream_unavailable";
71
71
  message: string;
72
72
  retryable: boolean;
73
73
  issues?: {
@@ -77,7 +77,7 @@ export declare class CatalogService {
77
77
  };
78
78
  };
79
79
  }, {
80
- code: "cache_unavailable" | "cancelled" | "cart_change_rejected" | "cart_change_unconfirmed" | "deadline_exceeded" | "invalid_data" | "invalid_input" | "not_authenticated" | "not_found" | "order_details_unavailable" | "quantity_below_minimum" | "queue_full" | "sale_rules_unknown" | "server_closed" | "upstream_unavailable";
80
+ code: "browser_unavailable" | "cache_unavailable" | "cancelled" | "cart_change_rejected" | "cart_change_unconfirmed" | "deadline_exceeded" | "invalid_data" | "invalid_input" | "not_authenticated" | "not_found" | "order_details_unavailable" | "quantity_below_minimum" | "queue_full" | "sale_rules_unknown" | "server_closed" | "upstream_unavailable";
81
81
  message: string;
82
82
  retryable: boolean;
83
83
  issues?: {
@@ -0,0 +1,25 @@
1
+ import type { ToolError } from '../domain/index.js';
2
+ import type { Logger } from '../ports.js';
3
+ import type { BrowserLauncher } from './browser.js';
4
+ /** The browser cannot be used yet; carries the message to show instead of a generic failure. */
5
+ export declare class BrowserUnavailable extends Error {
6
+ readonly toolError: ToolError;
7
+ constructor(toolError: ToolError);
8
+ }
9
+ export declare const isMissingBrowserError: (error: unknown) => boolean;
10
+ /** Run Playwright's installer for Chromium. With `onOutput`, its output is captured instead of inherited. */
11
+ export declare function runChromiumInstall(extraArgs?: readonly string[], onOutput?: (line: string) => void): Promise<number>;
12
+ /**
13
+ * Downloads Chromium the first time a launch finds it missing. Concurrent requests share one
14
+ * download, and a request that would run past its budget gets a "try again shortly" error.
15
+ */
16
+ export declare class ChromiumInstaller {
17
+ private readonly install;
18
+ private readonly logger;
19
+ private readonly maxWaitMs;
20
+ private pending;
21
+ constructor(install: () => Promise<void>, logger: Logger, maxWaitMs?: number);
22
+ private start;
23
+ private waitForInstall;
24
+ launcher(launch: BrowserLauncher): BrowserLauncher;
25
+ }
@@ -0,0 +1,74 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createRequire } from 'node:module';
3
+ import { dirname, join } from 'node:path';
4
+ import { requestTimeout } from './request.js';
5
+ /** The browser cannot be used yet; carries the message to show instead of a generic failure. */
6
+ export class BrowserUnavailable extends Error {
7
+ toolError;
8
+ constructor(toolError) {
9
+ super(toolError.message);
10
+ this.toolError = toolError;
11
+ this.name = 'BrowserUnavailable';
12
+ }
13
+ }
14
+ const unavailable = (message) => new BrowserUnavailable({ code: 'browser_unavailable', message, retryable: true });
15
+ export const isMissingBrowserError = (error) => error instanceof Error && /Executable doesn't exist/i.test(error.message);
16
+ /** Run Playwright's installer for Chromium. With `onOutput`, its output is captured instead of inherited. */
17
+ export function runChromiumInstall(extraArgs = [], onOutput) {
18
+ const cli = join(dirname(createRequire(import.meta.url).resolve('playwright/package.json')), 'cli.js');
19
+ const child = spawn(process.execPath, [cli, 'install', 'chromium', ...extraArgs], { stdio: onOutput ? ['ignore', 'pipe', 'pipe'] : 'inherit' });
20
+ if (onOutput) {
21
+ const forward = (chunk) => chunk.toString().split(/\r?\n|\r/).map(line => line.trim()).filter(Boolean).forEach(onOutput);
22
+ child.stdout?.on('data', forward);
23
+ child.stderr?.on('data', forward);
24
+ }
25
+ return new Promise(resolve => { child.on('error', () => resolve(1)); child.on('close', code => resolve(code ?? 1)); });
26
+ }
27
+ /**
28
+ * Downloads Chromium the first time a launch finds it missing. Concurrent requests share one
29
+ * download, and a request that would run past its budget gets a "try again shortly" error.
30
+ */
31
+ export class ChromiumInstaller {
32
+ install;
33
+ logger;
34
+ maxWaitMs;
35
+ pending;
36
+ constructor(install, logger, maxWaitMs = 25000) {
37
+ this.install = install;
38
+ this.logger = logger;
39
+ this.maxWaitMs = maxWaitMs;
40
+ }
41
+ start() {
42
+ this.pending ??= this.install().then(() => this.logger('Chromium is ready.'), error => { this.pending = undefined; throw error; });
43
+ return this.pending;
44
+ }
45
+ async waitForInstall() {
46
+ const pending = this.start();
47
+ // Leave time to launch the browser and do the actual work within the same request.
48
+ const budget = Math.max(0, requestTimeout(this.maxWaitMs + 10000) - 10000);
49
+ let timer;
50
+ const outcome = await Promise.race([pending.then(() => 'ready', () => 'failed'),
51
+ new Promise(resolve => { timer = setTimeout(() => resolve('waiting'), budget); })]);
52
+ clearTimeout(timer);
53
+ if (outcome === 'failed')
54
+ throw unavailable('Chromium could not be downloaded. Check the internet connection, or run `continentept-mcp install-browser`.');
55
+ if (outcome === 'waiting')
56
+ throw unavailable('Chromium is being downloaded for first use (about 150 MB). Try again in a minute.');
57
+ }
58
+ launcher(launch) {
59
+ return async (options) => {
60
+ if (this.pending)
61
+ await this.waitForInstall();
62
+ try {
63
+ return await launch(options);
64
+ }
65
+ catch (error) {
66
+ if (!isMissingBrowserError(error))
67
+ throw error;
68
+ this.logger('Chromium is not installed; downloading it now. This happens once.');
69
+ await this.waitForInstall();
70
+ return launch(options);
71
+ }
72
+ };
73
+ }
74
+ }
@@ -1,7 +1,9 @@
1
1
  # Configuration
2
2
 
3
3
  Settings come from environment variables and an optional private credentials file. They are
4
- read when the server starts.
4
+ read when the server starts. The Claude Desktop extension asks for your email and password
5
+ when you install it and passes them to the server for you; the rest of this page is for
6
+ manual setups.
5
7
 
6
8
  ## Variables
7
9
 
package/docs/tools.md CHANGED
@@ -195,6 +195,7 @@ credentials, cookies or file paths.
195
195
  | `cart_change_rejected` | The website rejected the cart change. |
196
196
  | `cart_change_unconfirmed` | The cart change could not be confirmed. |
197
197
  | `cache_unavailable` | The local favorites snapshot could not be read or written. |
198
+ | `browser_unavailable` | Chromium is still downloading for first use, or the download failed. Retryable. |
198
199
  | `cancelled`, `deadline_exceeded`, `server_closed`, `queue_full` | The request stopped before completing. |
199
200
  | `internal_error`, `invalid_result` | Unexpected server failure. Details are not exposed. |
200
201
 
@@ -5,7 +5,8 @@ without contacting the website.
5
5
 
6
6
  | Symptom | What to do |
7
7
  | --- | --- |
8
- | `Executable doesn't exist` or the browser fails to start | Run `npx -y continentept-mcp install-browser`. On Linux, add `--with-deps` to install system libraries. |
8
+ | `browser_unavailable`: Chromium is being downloaded | The first use downloads Chromium (about 150 MB). Wait a minute and try again. |
9
+ | `browser_unavailable`: Chromium could not be downloaded | Check the internet connection, then run `npx -y continentept-mcp install-browser` to see the download output. On Linux, add `--with-deps` to install system libraries. |
9
10
  | `not_authenticated` | Check that `get_session_status` reports credentials as configured, then run `continentept-mcp login` to test them. See [configuration](configuration.md). |
10
11
  | Sign-in keeps failing | Run `continentept-mcp login --show-browser` to watch the browser and complete any extra verification the website asks for. |
11
12
  | Status shows `authentication: unknown` | Nothing has been observed since the server started or since the saved session changed. Any account tool will establish it. |
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "continentept-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
+ "mcpName": "io.github.andredelgado/continente-mcp",
4
5
  "description": "MCP server for Continente.pt: search products, check sale rules, read favorites and orders, and make verified cart changes.",
5
6
  "keywords": [
6
7
  "mcp",
@@ -62,7 +63,8 @@
62
63
  "test": "node build/tools/run-tests.js",
63
64
  "test:browser": "npm run build && tsc -p tsconfig.tests.json && node build/tools/run-tests.js --browser",
64
65
  "test:package": "npm run build:tools && node build/tools/package-smoke.js",
65
- "prepack": "npm run build"
66
+ "prepack": "npm run build",
67
+ "build:bundle": "npm run build && node build/tools/build-bundle.js"
66
68
  },
67
69
  "dependencies": {
68
70
  "@modelcontextprotocol/sdk": "^1.32.1",
@@ -71,6 +73,7 @@
71
73
  "zod": "4.6.7"
72
74
  },
73
75
  "devDependencies": {
76
+ "@anthropic-ai/mcpb": "2.1.2",
74
77
  "@types/node": "^22.18.0",
75
78
  "typescript": "^7.0.2"
76
79
  }