continente-mcp 3.1.0 → 4.1.0

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/.env.example CHANGED
@@ -1,19 +1,29 @@
1
1
  # continente-mcp environment variables
2
- # Copy this to .env or set in your shell / launchd / systemd unit.
2
+ # Copy this to a private env file, for example:
3
+ # ~/.continente/credentials.env
4
+ #
5
+ # Keep the real file outside your repo, chmod 600, and never commit it.
6
+
7
+ # Required for automatic login.
8
+ # CONTINENTE_EMAIL=you@example.com
9
+ # CONTINENTE_PASSWORD=your-password
10
+
11
+ # Optional: use a different private env file. This one cannot go in the file
12
+ # itself; set it in your MCP client's server "env" instead.
13
+ # CONTINENTE_ENV_PATH=/absolute/path/to/credentials.env
14
+
15
+ # Set to false only for local debugging, so you can watch the login browser.
16
+ # CONTINENTE_LOGIN_HEADLESS=true
3
17
 
4
18
  # Path where cookies are stored (default: ~/.continente/cookies.json)
5
- # CONTINENTE_COOKIES_PATH=~/.continente/cookies.json
19
+ # Cookies are an internal session cache; you usually do not need to edit this.
20
+ # CONTINENTE_COOKIES_PATH=/absolute/path/to/cookies.json
6
21
 
7
22
  # If you run the MCP server on a second machine and sync cookies via a shared
8
23
  # folder (e.g. Obsidian vault, Syncthing, Dropbox), set this to the shared path
9
- # on the machine that runs the cookie reader. The reader will write cookies to
10
- # both the default path and this one whenever it runs.
24
+ # for automatic login to also write cookies there. These cookies give access
25
+ # to your account: only use a private folder you control.
11
26
  # CONTINENTE_VAULT_COOKIE_PATH=/path/to/shared/folder/cookies.json
12
27
 
13
- # Override the state directory (preferences, fingerprint, backups)
14
- # CONTINENTE_STATE_DIR=~/.continente
15
-
16
- # Webhook URL to notify when the website structure changes (check-change.js).
17
- # Supports anything that accepts a POST with { "text": "..." } — Slack, Discord,
18
- # n8n, Make, etc. If not set, changes are logged to stdout only.
19
- # CONTINENTE_ALERT_WEBHOOK=https://hooks.slack.com/services/...
28
+ # Override the state directory (preferences and default cookies)
29
+ # CONTINENTE_STATE_DIR=/absolute/path/to/state
package/README.md CHANGED
@@ -1,325 +1,64 @@
1
- # continente-mcp
1
+ # Continente MCP
2
2
 
3
- An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server for [Continente.pt](https://www.continente.pt) — Portugal's largest supermarket chain.
3
+ Let your AI assistant search Continente.pt and fill your basket with the products you usually buy.
4
4
 
5
- Lets your AI assistant search products, manage your shopping cart, and browse order history using your real Continente account.
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
6
 
7
- > **⚠️ Stability note:** This server works by automating a real browser session against Continente's website using CSS selectors. If Continente updates their site structure, some tools may break. See [Detecting changes](#detecting-changes).
7
+ ## Features
8
8
 
9
- ---
9
+ - **Search that knows your habits** — catalogue results put your saved favourites first.
10
+ - **Hands-free basket** — add, change and remove items; every change is checked against the basket afterwards.
11
+ - **Order history** — read recent orders and see your most-bought products.
12
+ - **Automatic login** — expired sessions sign in again on their own from a private credentials file.
13
+ - **You stay in control** — checkout and payment always happen on Continente.pt.
10
14
 
11
- ## What it can do
15
+ ## Install
12
16
 
13
- | Tool | Description |
14
- |------|-------------|
15
- | `search_products` | Search the catalogue — results ranked by your favourites |
16
- | `get_favorites` | List your saved favourite products |
17
- | `refresh_favorites` | Re-sync favourites from the website |
18
- | `get_cart` | View your current basket |
19
- | `add_to_cart` | Add a product by ID |
20
- | `get_order_history` | Recent orders with product lines |
21
- | `get_most_bought` | Products you order most often (scans all orders — slow) |
22
- | `close_session` | Close the browser and free memory |
23
-
24
- ---
25
-
26
- ## Requirements
27
-
28
- - [Node.js](https://nodejs.org/) 18 or later
29
- - [Python](https://www.python.org/) 3.10+ (for the cookie export script)
30
- - A Continente.pt account, logged in on one of the [supported browsers](#supported-browsers)
31
-
32
- ---
33
-
34
- ## Setup
35
-
36
- ### 1. Clone and install
37
-
38
- The setup requires the repository — the Python cookie scripts live here and are needed before the server can authenticate.
39
-
40
- ```bash
41
- git clone https://github.com/davidmarqu3s/continente-mcp
42
- cd continente-mcp
43
- npm install
44
- ```
45
-
46
- Install the Chromium browser that Playwright uses internally:
47
-
48
- ```bash
49
- npm run setup
50
- # or: npx playwright install chromium --with-deps
51
- ```
52
-
53
- > **Linux note:** `--with-deps` installs OS-level dependencies via apt. On macOS and Windows this flag is a no-op — `npx playwright install chromium` is sufficient.
54
-
55
- Once set up, you can run the server either via the local clone or via `npx continente-mcp` — both work in your MCP client config (see [step 3](#3-configure-your-mcp-client)).
56
-
57
- ### 2. Export your cookies
58
-
59
- The server authenticates using cookies from your existing browser session — no password handling, no OAuth flow. You need to be **logged into Continente.pt** in your browser first.
60
-
61
- Install the cookie reader dependencies:
62
-
63
- ```bash
64
- python3 -m pip install browser-cookie3 requests # macOS / Linux
65
- python -m pip install browser-cookie3 requests # Windows
66
- ```
67
-
68
- Export your cookies:
69
-
70
- ```bash
71
- python3 continente-cookie-reader.py # macOS / Linux
72
- python continente-cookie-reader.py # Windows
73
- # Auto-detecting browser with Continente cookies...
74
- # Found 20 cookies in chrome.
75
- # Saved 20 cookies → ~/.continente/cookies.json (browser: chrome)
76
- # On Windows: %USERPROFILE%\.continente\cookies.json
77
- ```
78
-
79
- To specify a browser explicitly:
80
-
81
- ```bash
82
- python3 continente-cookie-reader.py --browser firefox
83
- python3 continente-cookie-reader.py --list-browsers # see all supported browsers
84
- ```
85
-
86
- Cookies are saved to `~/.continente/cookies.json` (`%USERPROFILE%\.continente\cookies.json` on Windows) with owner-only read permissions.
87
-
88
- ### 3. Configure your MCP client
89
-
90
- This server works with any [MCP-compatible client](https://modelcontextprotocol.io/clients). The config format is the same across most of them — add an entry under `mcpServers` pointing to the server command.
91
-
92
- **Via npx (no local install needed):**
93
-
94
- ```json
95
- {
96
- "mcpServers": {
97
- "continente": {
98
- "command": "npx",
99
- "args": ["continente-mcp"]
100
- }
101
- }
102
- }
103
- ```
104
-
105
- **Via local clone:**
106
-
107
- ```json
108
- {
109
- "mcpServers": {
110
- "continente": {
111
- "command": "node",
112
- "args": ["/path/to/continente-mcp/src/index.js"]
113
- }
114
- }
115
- }
116
- ```
117
-
118
- Where to put this config:
119
-
120
- | Client | Config file |
121
- |--------|-------------|
122
- | Claude Desktop (Mac) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
123
- | Claude Desktop (Windows) | `%APPDATA%\Claude\claude_desktop_config.json` |
124
- | Cursor (Mac/Linux) | `.cursor/mcp.json` in your project, or `~/.cursor/mcp.json` globally |
125
- | Cursor (Windows) | `.cursor\mcp.json` in your project, or `%USERPROFILE%\.cursor\mcp.json` globally |
126
- | Windsurf (Mac/Linux) | `~/.codeium/windsurf/mcp_config.json` |
127
- | Windsurf (Windows) | `%USERPROFILE%\.codeium\windsurf\mcp_config.json` |
128
- | Other clients | See your client's MCP documentation |
129
-
130
- ---
131
-
132
- ## Keeping the session alive
133
-
134
- Continente sessions expire after a period of inactivity. Run the keepalive script on a schedule to prevent this:
135
-
136
- **macOS (launchd), every 20 minutes:**
137
-
138
- Create `~/Library/LaunchAgents/com.continente.keepalive.plist`:
139
-
140
- ```xml
141
- <?xml version="1.0" encoding="UTF-8"?>
142
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
143
- <plist version="1.0">
144
- <dict>
145
- <key>Label</key>
146
- <string>com.continente.keepalive</string>
147
- <key>ProgramArguments</key>
148
- <array>
149
- <string>/usr/bin/python3</string>
150
- <string>/path/to/continente-keepalive.py</string>
151
- </array>
152
- <key>StartInterval</key>
153
- <integer>1200</integer>
154
- <key>StandardOutPath</key>
155
- <string>/tmp/continente-keepalive.log</string>
156
- <key>StandardErrorPath</key>
157
- <string>/tmp/continente-keepalive.log</string>
158
- </dict>
159
- </plist>
160
- ```
161
-
162
- ```bash
163
- launchctl load ~/Library/LaunchAgents/com.continente.keepalive.plist
164
- ```
165
-
166
- **Linux / WSL (cron):**
17
+ Requires Node.js 20.18.1 or later. Install the browser the server uses (on Linux, add `--with-deps`):
167
18
 
168
19
  ```bash
169
- crontab -e
170
- # Add:
171
- */20 * * * * python3 /path/to/continente-keepalive.py >> ~/.continente/keepalive.log 2>&1
20
+ npx -y -p continente-mcp playwright install chromium
172
21
  ```
173
22
 
174
- **Windows (Task Scheduler):**
175
-
176
- ```powershell
177
- schtasks /create /tn "ContinenteKeepalive" /tr "python C:\path\to\continente-keepalive.py" /sc minute /mo 20 /f
178
- ```
179
-
180
- Or open Task Scheduler and create a task that runs `python C:\path\to\continente-keepalive.py` every 20 minutes.
181
-
182
- If the session expires, the keepalive script will automatically re-read cookies from your browser.
183
-
184
- ---
185
-
186
- ## Security
187
-
188
- - **Cookies give full account access** — they can view your address, manage your cart, and see your order history. Treat them like a password.
189
- - **macOS / Linux:** Cookies are stored at `~/.continente/cookies.json` with owner-only read permissions (`chmod 600`).
190
- - **Windows:** Cookies are stored at `%USERPROFILE%\.continente\cookies.json`. To restrict access: `icacls "%USERPROFILE%\.continente\cookies.json" /inheritance:r /grant:r "%USERNAME%:R"`
191
- - This path is excluded from git via `.gitignore`. Never commit it.
192
- - Cookies expire roughly every 30 days. Re-run `continente-cookie-reader.py` to refresh them.
23
+ For favourites, basket and order history, create `~/.continente/credentials.env` with your Continente login and make it private to your user (`chmod 600` on macOS/Linux). Search works without it.
193
24
 
194
- ---
195
-
196
- ## Detecting changes
197
-
198
- Run `check-change.js` on a schedule to get notified if Continente updates their site structure in a way that might break scraping:
199
-
200
- ```bash
201
- # First run saves a structural fingerprint
202
- node check-change.js
203
- ```
204
-
205
- **Linux / WSL (cron):**
206
- ```bash
207
- # Daily at 9am
208
- 0 9 * * * node /path/to/check-change.js >> ~/.continente/change-detector.log 2>&1
209
25
  ```
210
-
211
- **macOS (launchd):** Add a plist similar to the keepalive one above, with `StartInterval` set to `86400`.
212
-
213
- **Windows (Task Scheduler):**
214
- ```powershell
215
- schtasks /create /tn "ContinenteChangeDetector" /tr "node C:\path\to\check-change.js" /sc daily /st 09:00 /f
26
+ CONTINENTE_EMAIL=you@example.com
27
+ CONTINENTE_PASSWORD=your-password
216
28
  ```
217
29
 
218
- To receive a webhook notification (Slack, Discord, n8n, etc.) on change, set `CONTINENTE_ALERT_WEBHOOK` in your environment or MCP client config:
219
-
220
- ```bash
221
- # macOS / Linux
222
- export CONTINENTE_ALERT_WEBHOOK=https://hooks.slack.com/services/...
223
-
224
- # Windows cmd
225
- set CONTINENTE_ALERT_WEBHOOK=https://hooks.slack.com/services/...
226
-
227
- # Windows PowerShell
228
- $env:CONTINENTE_ALERT_WEBHOOK = "https://hooks.slack.com/services/..."
229
- ```
230
-
231
- Or add it to your MCP client config under `env` (platform-neutral):
30
+ Then add the server to your MCP client and restart it:
232
31
 
233
32
  ```json
234
33
  {
235
34
  "mcpServers": {
236
- "continente": {
237
- "command": "npx",
238
- "args": ["continente-mcp"],
239
- "env": {
240
- "CONTINENTE_ALERT_WEBHOOK": "https://hooks.slack.com/services/..."
241
- }
242
- }
35
+ "continente": { "command": "npx", "args": ["-y", "continente-mcp"] }
243
36
  }
244
37
  }
245
38
  ```
246
39
 
247
- ---
248
-
249
- ## Utilities
250
-
251
- **Back up order history** (incremental — only fetches new orders):
252
-
253
- ```bash
254
- node continente-backup.js
255
- # → ~/.continente/orders-backup.json
256
- # → %USERPROFILE%\.continente\orders-backup.json (Windows)
257
- ```
258
-
259
- ---
260
-
261
- ## Supported browsers
262
-
263
- `continente-cookie-reader.py` can read cookies from:
264
-
265
- | Browser | macOS | Windows | Linux |
266
- |---------|-------|---------|-------|
267
- | Chrome | ✓ | ✓ | ✓ |
268
- | Edge | ✓ | ✓ | ✓ |
269
- | Brave | ✓ | ✓ | ✓ |
270
- | Firefox | ✓ | ✓ | ✓ |
271
- | Arc | ✓ | — | — |
272
- | Chromium | ✓ | ✓ | ✓ |
273
- | Vivaldi | ✓ | ✓ | ✓ |
274
- | Opera | ✓ | ✓ | ✓ |
275
- | Safari | ✓ | — | — |
276
-
277
- Powered by [browser-cookie3](https://github.com/borisbabic/browser_cookie3).
278
-
279
- ---
280
-
281
- ## Environment variables
282
-
283
- See [`.env.example`](./.env.example) for all options. The main ones:
40
+ To run from source instead, clone the repository, run `npm install && npm run setup`, and use `node /absolute/path/to/continente-mcp/src/index.js` as the command.
284
41
 
285
- | Variable | Default | Description |
286
- |----------|---------|-------------|
287
- | `CONTINENTE_COOKIES_PATH` | `~/.continente/cookies.json` | Where cookies are read/written |
288
- | `CONTINENTE_STATE_DIR` | `~/.continente` | Directory for preferences, fingerprint, backups |
289
- | `CONTINENTE_ALERT_WEBHOOK` | — | Webhook URL for change detection alerts |
290
- | `CONTINENTE_VAULT_COOKIE_PATH` | — | Optional secondary path to also write cookies to (for multi-machine sync) |
42
+ ## Use
291
43
 
292
- > **Windows:** Replace `~` with `%USERPROFILE%` (cmd) or `$env:USERPROFILE` (PowerShell). Node.js accepts both `/` and `\` as path separators.
293
-
294
- ---
295
-
296
- ## Skills
297
-
298
- The [`skills/`](./skills/) directory contains ready-made agent skills for clients that support them (e.g. Claude Code).
299
-
300
- ### groceries
301
-
302
- Adds items to the basket by name. Matches against your favourites and order history to pick the right product variant automatically.
303
-
304
- Install it in Claude Code:
305
-
306
- ```bash
307
- # From the repo root
308
- claude skill install skills/groceries
309
- ```
310
-
311
- Then use it:
312
-
313
- ```
314
- /groceries queijo flamengo, leite mimosa, ovos
315
- ```
44
+ Ask your assistant things like "add milk, eggs and bread to my Continente basket". Run `refresh_favorites` once so searches know what you buy.
316
45
 
317
- Or naturally: *"add some manteiga and pão de forma to my Continente cart"*
46
+ | Tool | Purpose |
47
+ | --- | --- |
48
+ | `search_products` | Search the catalogue, favourites first |
49
+ | `get_favorites` / `refresh_favorites` | Read cached favourites / refresh them from your account |
50
+ | `get_cart` | Read the basket |
51
+ | `add_to_cart` / `update_cart_item` | Add a product / set its quantity (zero removes it) |
52
+ | `get_order_history` / `get_most_bought` | Read recent orders / your most-bought products |
53
+ | `close_session` | Close the browser session |
318
54
 
319
- See [`skills/groceries/SKILL.md`](./skills/groceries/SKILL.md) for full details.
55
+ ## More
320
56
 
321
- ---
57
+ - **Agents:** the [groceries skill](skills/groceries/SKILL.md) teaches assistants to pick the products you usually buy.
58
+ - **Settings:** custom credential, state and cookie paths are listed in [`.env.example`](.env.example).
59
+ - **Troubleshooting:** [docs/troubleshooting.md](docs/troubleshooting.md).
60
+ - **Developers:** [docs/development.md](docs/development.md) and [docs/releases.md](docs/releases.md).
322
61
 
323
62
  ## License
324
63
 
325
- [MIT](./LICENSE)
64
+ [MIT](LICENSE)
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env node
2
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
3
+ import { dirname, join } from 'path';
4
+ import { homedir } from 'os';
5
+ import { pathToFileURL } from 'node:url';
6
+ import { chromium } from 'playwright';
7
+
8
+ const CONTINENTE_LOGIN_URL = 'https://www.continente.pt/login/';
9
+ const CONTINENTE_CHECK_URL = 'https://www.continente.pt/conta/encomendas/';
10
+ const DEFAULT_ENV = process.env;
11
+
12
+ function log(message) {
13
+ console.error(`[continente-auto-login] ${message}`);
14
+ }
15
+
16
+ export function writeCookies(path, cookies) {
17
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
18
+ // Create owner-only; chmod also tightens files created by older versions.
19
+ writeFileSync(path, JSON.stringify(cookies, null, 2), { mode: 0o600 });
20
+ chmodSync(path, 0o600);
21
+ }
22
+
23
+ function unquoteEnvValue(value) {
24
+ const trimmed = value.trim();
25
+ if (
26
+ (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
27
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))
28
+ ) {
29
+ return trimmed.slice(1, -1);
30
+ }
31
+ return trimmed;
32
+ }
33
+
34
+ function parseEnvFile(contents) {
35
+ const parsed = {};
36
+ for (const line of contents.split(/\r?\n/)) {
37
+ let raw = line.trim();
38
+ if (!raw || raw.startsWith('#')) continue;
39
+ if (raw.startsWith('export ')) raw = raw.slice('export '.length).trim();
40
+ const index = raw.indexOf('=');
41
+ if (index === -1) continue;
42
+ const key = raw.slice(0, index).trim();
43
+ const value = raw.slice(index + 1);
44
+ if (/^[A-Z_][A-Z0-9_]*$/.test(key)) {
45
+ parsed[key] = unquoteEnvValue(value);
46
+ }
47
+ }
48
+ return parsed;
49
+ }
50
+
51
+ export function credentialEnvPath(env = DEFAULT_ENV) {
52
+ const home = env.HOME || env.USERPROFILE || (env === DEFAULT_ENV ? homedir() : null);
53
+ const stateDir = env.CONTINENTE_STATE_DIR || (home ? join(home, '.continente') : null);
54
+ return env.CONTINENTE_ENV_PATH || (stateDir ? join(stateDir, 'credentials.env') : null);
55
+ }
56
+
57
+ export function loadCredentialEnv(env = DEFAULT_ENV) {
58
+ const file = credentialEnvPath(env);
59
+ if (!file || !existsSync(file)) {
60
+ return { ...env };
61
+ }
62
+ return { ...parseEnvFile(readFileSync(file, 'utf8')), ...env, CONTINENTE_ENV_PATH: file };
63
+ }
64
+
65
+ export function resolveStatePaths(env = DEFAULT_ENV) {
66
+ const settings = loadCredentialEnv(env);
67
+ const stateDir = settings.CONTINENTE_STATE_DIR || join(settings.HOME || settings.USERPROFILE || homedir(), '.continente');
68
+ return { stateDir, cookieFile: settings.CONTINENTE_COOKIES_PATH || join(stateDir, 'cookies.json') };
69
+ }
70
+
71
+ function syncVault(cookies, vaultCookiePath, logger) {
72
+ if (!vaultCookiePath) return;
73
+ const next = JSON.stringify(cookies, null, 2);
74
+ if (existsSync(vaultCookiePath) && readFileSync(vaultCookiePath, 'utf8') === next) {
75
+ logger('Vault unchanged.');
76
+ return;
77
+ }
78
+ writeCookies(vaultCookiePath, cookies);
79
+ logger('Vault cookies updated.');
80
+ }
81
+
82
+ export function getCredentialStatus(env = DEFAULT_ENV) {
83
+ const mergedEnv = loadCredentialEnv(env);
84
+ return {
85
+ hasEmail: Boolean(mergedEnv.CONTINENTE_EMAIL),
86
+ hasPassword: Boolean(mergedEnv.CONTINENTE_PASSWORD),
87
+ ready: Boolean(mergedEnv.CONTINENTE_EMAIL && mergedEnv.CONTINENTE_PASSWORD),
88
+ };
89
+ }
90
+
91
+ export async function isLoggedIn(page) {
92
+ const response = await page.goto(CONTINENTE_CHECK_URL, { waitUntil: 'domcontentloaded', timeout: 20000 });
93
+ if (!response?.ok() || page.url().includes('/login')) return false;
94
+ const session = await page.context().request.get(
95
+ 'https://www.continente.pt/on/demandware.store/Sites-continente-Site/default/Cart-MiniCartShow',
96
+ { headers: { 'X-Requested-With': 'XMLHttpRequest' }, timeout: 10000 }
97
+ );
98
+ if (!session.ok()) return false;
99
+ const payload = await session.json().catch(() => null);
100
+ return payload?.resources?.customerAuthenticated === true;
101
+ }
102
+
103
+ async function fillFirst(page, selectors, value, label) {
104
+ for (const selector of selectors) {
105
+ const input = await page.waitForSelector(selector, { timeout: 3000 }).catch(() => null);
106
+ if (input) {
107
+ await input.fill(value);
108
+ return;
109
+ }
110
+ }
111
+ throw new Error(`Could not find ${label} input`);
112
+ }
113
+
114
+ async function clickFirst(page, selectors) {
115
+ for (const selector of selectors) {
116
+ const button = await page.$(selector);
117
+ if (button) {
118
+ await button.click();
119
+ return true;
120
+ }
121
+ }
122
+ await page.keyboard.press('Enter');
123
+ return false;
124
+ }
125
+
126
+ async function acceptCookies(page) {
127
+ await page.getByRole('button', { name: /PERMITIR TODOS|Permitir todos/i })
128
+ .click({ timeout: 5000 })
129
+ .catch(() => {});
130
+ }
131
+
132
+ async function findLoginFrame(page) {
133
+ const deadline = Date.now() + 15000;
134
+ while (Date.now() < deadline) {
135
+ const frame = page.frames().find((candidate) =>
136
+ candidate.url().includes('login.continente.pt/user-register')
137
+ );
138
+ if (frame) {
139
+ return frame;
140
+ }
141
+ await page.waitForTimeout(500);
142
+ }
143
+ throw new Error('Could not find Continente login frame');
144
+ }
145
+
146
+ export async function autoLogin({
147
+ env = DEFAULT_ENV,
148
+ log: logger = log,
149
+ } = {}) {
150
+ const mergedEnv = loadCredentialEnv(env);
151
+ const email = mergedEnv.CONTINENTE_EMAIL;
152
+ const password = mergedEnv.CONTINENTE_PASSWORD;
153
+
154
+ if (!getCredentialStatus(mergedEnv).ready) {
155
+ logger('Missing CONTINENTE_EMAIL or CONTINENTE_PASSWORD.');
156
+ return { success: false, error: 'missing_credentials' };
157
+ }
158
+
159
+ const browser = await chromium.launch({
160
+ headless: mergedEnv.CONTINENTE_LOGIN_HEADLESS !== 'false',
161
+ });
162
+
163
+ try {
164
+ const context = await browser.newContext({
165
+ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
166
+ locale: 'pt-PT',
167
+ });
168
+ const page = await context.newPage();
169
+
170
+ logger('Opening login page.');
171
+ await page.goto(CONTINENTE_LOGIN_URL, { waitUntil: 'domcontentloaded', timeout: 30000 });
172
+ await acceptCookies(page);
173
+ const loginFrame = await findLoginFrame(page);
174
+
175
+ logger('Entering email.');
176
+ await fillFirst(loginFrame, [
177
+ '#userNameCC',
178
+ 'input[name="userNameCC"]',
179
+ 'input[type="email"]',
180
+ 'input[name="email"]',
181
+ 'input[name="username"]',
182
+ 'input[type="text"]',
183
+ ], email, 'email');
184
+
185
+ await clickFirst(loginFrame, [
186
+ 'button[type="submit"]',
187
+ 'button:has-text("Avançar")',
188
+ 'button:has-text("Continuar")',
189
+ ]);
190
+
191
+ logger('Entering password.');
192
+ await page.waitForTimeout(1500);
193
+ await fillFirst(loginFrame, [
194
+ '#password_input',
195
+ 'input[name="password_input"]',
196
+ 'input[type="password"]',
197
+ 'input[name="password"]',
198
+ ], password, 'password');
199
+
200
+ await clickFirst(loginFrame, [
201
+ 'button[type="submit"]',
202
+ 'button:has-text("Avançar")',
203
+ 'button:has-text("Entrar")',
204
+ 'button:has-text("Login")',
205
+ ]);
206
+
207
+ await page.waitForTimeout(4000);
208
+ await page.waitForLoadState('domcontentloaded', { timeout: 15000 }).catch(() => {});
209
+
210
+ if (!(await isLoggedIn(page))) {
211
+ throw new Error('Login did not reach an authenticated Continente page');
212
+ }
213
+
214
+ const cookies = await context.cookies();
215
+ writeCookies(resolveStatePaths(mergedEnv).cookieFile, cookies);
216
+ syncVault(cookies, mergedEnv.CONTINENTE_VAULT_COOKIE_PATH, logger);
217
+ logger(`Saved ${cookies.length} cookies.`);
218
+ return { success: true, cookies: cookies.length };
219
+ } finally {
220
+ await browser.close();
221
+ }
222
+ }
223
+
224
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
225
+ autoLogin().then((result) => {
226
+ if (!result.success) {
227
+ process.exit(1);
228
+ }
229
+ }).catch((error) => {
230
+ log(error.message);
231
+ process.exit(1);
232
+ });
233
+ }
package/package.json CHANGED
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "name": "continente-mcp",
3
- "version": "3.1.0",
4
- "description": "MCP server for Continente.pt — search products, manage your cart, and browse order history from your AI assistant.",
3
+ "version": "4.1.0",
4
+ "description": "MCP server for Continente.pt — search products, manage your cart, and browse order history.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "bin": {
8
8
  "continente-mcp": "src/index.js"
9
9
  },
10
10
  "engines": {
11
- "node": ">=18.0.0"
11
+ "node": ">=20.18.1"
12
12
  },
13
13
  "scripts": {
14
14
  "start": "node src/index.js",
15
15
  "setup": "playwright install chromium --with-deps",
16
- "test": "echo \"No tests yet\" && exit 0"
16
+ "test": "node --test",
17
+ "check:release": "node scripts/check-release.js"
17
18
  },
18
19
  "keywords": [
19
20
  "mcp",
@@ -39,16 +40,13 @@
39
40
  "files": [
40
41
  "src/",
41
42
  "skills/",
42
- "continente-cookie-reader.py",
43
- "continente-keepalive.py",
44
- "continente-backup.js",
45
- "check-change.js",
43
+ "continente-auto-login.js",
46
44
  ".env.example",
47
45
  "LICENSE",
48
46
  "README.md"
49
47
  ],
50
48
  "dependencies": {
51
- "@modelcontextprotocol/sdk": "^1.29.0",
49
+ "@modelcontextprotocol/sdk": "^1.32.1",
52
50
  "cheerio": "^1.2.0",
53
51
  "playwright": "^1.59.1"
54
52
  }