auchan-mcp 0.2.1 → 0.3.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 ADDED
@@ -0,0 +1,21 @@
1
+ # auchan-mcp: variáveis de ambiente
2
+ # Com o plugin supermercados-mcp, o ficheiro é criado e aberto sozinho na primeira vez:
3
+ # Windows: %USERPROFILE%\.supermercados-mcp\credentials.env
4
+ # macOS/Linux: ~/.supermercados-mcp/credentials.env
5
+ # Sem o plugin, podes pôr as duas linhas abaixo nesse ficheiro ou em ~/.auchan-mcp/credentials.env.
6
+ # Esse ficheiro nunca vai para o git. A pesquisa funciona sem ele.
7
+
8
+ # Necessárias para o carrinho (get_cart, add_to_cart, update_cart_item).
9
+ # AUCHAN_EMAIL=tu@exemplo.pt
10
+ # AUCHAN_PASSWORD=a-tua-palavra-passe
11
+
12
+ # As seguintes definem-se no "env" do servidor no cliente MCP, não no ficheiro acima.
13
+
14
+ # Outro ficheiro de credenciais (por omissão: o do plugin e depois ~/.auchan-mcp/credentials.env).
15
+ # AUCHAN_ENV_PATH=C:\caminho\para\credentials.env
16
+
17
+ # Outra pasta para a sessão guardada e a cache (por omissão: ~/.auchan-mcp).
18
+ # AUCHAN_STATE_DIR=C:\caminho\para\pasta
19
+
20
+ # Põe false só para ver o browser durante o login, ao procurar um problema.
21
+ # AUCHAN_LOGIN_HEADLESS=true
package/README.md CHANGED
@@ -10,6 +10,7 @@ Feito à imagem do [continente-mcp](https://github.com/davidmarqu3s/continente-m
10
10
 
11
11
  - **Pesquisa sem conta**: nome, marca, preço, preço e preço por litro, quilo ou unidade.
12
12
  - **Carrinho**: ver, juntar e mudar quantidades; cada alteração é confirmada no carrinho a seguir.
13
+ - **Entra sozinho**: usa o email e a palavra-passe de um ficheiro teu, em segundo plano e sem janela, e volta a entrar sempre que o Auchan termina a sessão.
13
14
  - **Respeita o site**: no máximo um pedido por segundo, resultados guardados durante 6 horas e um user-agent que diz quem é.
14
15
  - **Tu decides**: a finalização da encomenda e o pagamento fazem-se sempre no Auchan.pt. O servidor não tem nenhuma ferramenta para isso.
15
16
 
@@ -31,7 +32,16 @@ Precisas do Node.js 20.18.1 ou mais recente e de um cliente MCP, por exemplo o C
31
32
  }
32
33
  ```
33
34
 
34
- A pesquisa funciona logo, sem conta. Para o carrinho não crias nada: quando o assistente precisar dele, abre-se uma janela do Edge ou do Chrome na página de login do Auchan. Entras tu, a janela fecha e ficas ligado. A palavra-passe nunca passa pelo servidor nem fica guardada: só a sessão, no teu computador (`%USERPROFILE%\.auchan-mcp\cookies.json`). Se aparecer um CAPTCHA, resolves tu na janela.
35
+ A pesquisa funciona logo, sem conta. Para o carrinho, cria o ficheiro `%USERPROFILE%\.auchan-mcp\credentials.env` (no macOS e Linux, `~/.auchan-mcp/credentials.env`) com duas linhas, sem aspas e sem espaços à volta do `=`:
36
+
37
+ ```
38
+ AUCHAN_EMAIL=o-teu-email
39
+ AUCHAN_PASSWORD=a-tua-palavra-passe
40
+ ```
41
+
42
+ Também lê `~/.supermercados-mcp/credentials.env`, o ficheiro que o plugin [supermercados-mcp](https://github.com/vasco-branco06/supermercados-mcp) cria e abre sozinho na primeira vez, e o que `AUCHAN_ENV_PATH` indicar. O Auchan só aceita produtos no carrinho depois de escolheres uma vez, no site, entrega em casa ou levantamento (com o código postal); essa escolha fica guardada na tua conta.
43
+
44
+ A palavra-passe fica em texto simples nesse ficheiro, na tua pasta de utilizador. Não o partilhes. Só é lida para preencher o formulário de login e nunca aparece nas respostas nem nos registos. A sessão (cookies e armazenamento local) fica em `%USERPROFILE%\.auchan-mcp\cookies.json`.
35
45
 
36
46
  ## Usar
37
47
 
@@ -42,12 +52,16 @@ Pede coisas como "procura leite meio-gordo no Auchan" ou "junta 2 pacotes ao car
42
52
  | `search_products` | Pesquisar o catálogo (devolve JSON) |
43
53
  | `get_cart` | Ver o carrinho |
44
54
  | `add_to_cart` / `update_cart_item` | Juntar um produto / mudar a quantidade (0 tira-o) |
45
- | `login` | Ligar a tua conta: abre a janela de login (as ferramentas do carrinho fazem-no sozinhas se for preciso) |
46
- | `logout` | Desligar a conta: apaga a sessão guardada neste computador |
55
+ | `login` | Verificar ou renovar a sessão com o ficheiro de credenciais (as ferramentas do carrinho fazem-no sozinhas) |
56
+ | `logout` | Desligar a conta: apaga a sessão guardada neste computador (o ficheiro de credenciais fica) |
47
57
  | `close_session` | Libertar recursos |
48
58
 
59
+ Com o servidor a correr, faz um pedido leve de 10 em 10 minutos para o Auchan não fechar a sessão por inatividade, e guarda o que o site renova.
60
+
49
61
  ## Mais
50
62
 
63
+ - **Sem ficheiro de credenciais:** existe o ramo [`login-por-janela`](https://github.com/vasco-branco06/supermercados-mcp/tree/login-por-janela), em que a pessoa entra numa janela do browser e a palavra-passe nunca é guardada.
64
+ - **Definições:** variáveis em [`.env.example`](.env.example).
51
65
  - **Problemas:** [docs/troubleshooting.md](docs/troubleshooting.md).
52
66
  - **Testes:** `npm test` corre sem internet. `LIVE=1` liga um teste que faz um pedido real ao Auchan.pt (no PowerShell: `$env:LIVE=1; npm test`).
53
67
 
package/dist/index.js CHANGED
@@ -176,21 +176,47 @@ async function fetchPage(url2, { fetchImpl = fetch, gate = throttle } = {}) {
176
176
  }
177
177
 
178
178
  // src/paths.js
179
+ import { existsSync, readFileSync as readFileSync2 } from "node:fs";
179
180
  import { homedir } from "node:os";
180
181
  import { join } from "node:path";
181
182
  function resolveStatePaths(env = process.env) {
182
- const stateDir = env.AUCHAN_STATE_DIR || join(env.HOME || env.USERPROFILE || homedir(), ".auchan-mcp");
183
+ const home = env.HOME || env.USERPROFILE || homedir();
184
+ const stateDir = env.AUCHAN_STATE_DIR || join(home, ".auchan-mcp");
183
185
  return {
184
186
  stateDir,
185
187
  cacheDir: join(stateDir, "cache"),
188
+ // Where the plugin's first run creates the template, shared by every supermarket.
189
+ sharedCredentialFile: env.SUPERMERCADOS_MCP_CREDENTIALS || join(env.SUPERMERCADOS_MCP_DIR || join(home, ".supermercados-mcp"), "credentials.env"),
190
+ credentialFile: join(stateDir, "credentials.env"),
186
191
  cookieFile: join(stateDir, "cookies.json")
187
192
  };
188
193
  }
194
+ function parseEnvFile(contents) {
195
+ const parsed = {};
196
+ for (const line of contents.split(/\r?\n/)) {
197
+ const raw = line.trim().replace(/^export\s+/, "");
198
+ const index = raw.indexOf("=");
199
+ if (!raw || raw.startsWith("#") || index === -1) continue;
200
+ const key = raw.slice(0, index).trim();
201
+ const value = raw.slice(index + 1).trim().replace(/^(["'])(.*)\1$/, "$2");
202
+ if (/^[A-Z_][A-Z0-9_]*$/.test(key)) parsed[key] = value;
203
+ }
204
+ return parsed;
205
+ }
206
+ function loadCredentials(env = process.env) {
207
+ const { credentialFile, sharedCredentialFile } = resolveStatePaths(env);
208
+ for (const file2 of [env.AUCHAN_ENV_PATH, sharedCredentialFile, credentialFile]) {
209
+ if (!file2 || !existsSync(file2)) continue;
210
+ const { AUCHAN_EMAIL: email3, AUCHAN_PASSWORD: password } = parseEnvFile(readFileSync2(file2, "utf8"));
211
+ if (email3 && password) return { email: email3, password };
212
+ }
213
+ return null;
214
+ }
189
215
 
190
216
  // src/search.js
191
217
  import * as cheerio2 from "cheerio";
192
218
  import { createHash } from "node:crypto";
193
- import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
219
+ import { mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
194
220
  import { join as join2 } from "node:path";
195
221
 
196
222
  // ../../node_modules/zod/v4/classic/external.js
@@ -20010,7 +20036,7 @@ var searchUrl = (query) => `${AUCHAN_BASE}/pt/pesquisa?q=${encodeURIComponent(qu
20010
20036
  var cacheFile = (cacheDir, query) => join2(cacheDir, `${createHash("sha256").update(query.trim().toLowerCase()).digest("hex").slice(0, 32)}.json`);
20011
20037
  function readCache(file2, now) {
20012
20038
  try {
20013
- const entry = JSON.parse(readFileSync2(file2, "utf8"));
20039
+ const entry = JSON.parse(readFileSync3(file2, "utf8"));
20014
20040
  if (now - Date.parse(entry.fetched_at) < CACHE_TTL_MS && Array.isArray(entry.raw)) return entry;
20015
20041
  } catch {
20016
20042
  }
@@ -20035,23 +20061,30 @@ async function searchProducts(query, limit = 10, { cacheDir, fetchImpl, now = Da
20035
20061
  }
20036
20062
 
20037
20063
  // src/session.js
20038
- import { chmodSync, existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
20064
+ import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
20039
20065
  import { dirname } from "node:path";
20040
20066
  var LOGIN_URL = `${AUCHAN_BASE}/on/demandware.store/Sites-AuchanPT-Site/pt_PT/Login-OAuthLogin?oauthProvider=Salesforce&oauthLoginTargetEndPoint=1`;
20041
20067
  var DELIVERY_URL = `${AUCHAN_BASE}/on/demandware.store/Sites-AuchanPT-Site/pt_PT/Delivery-Header`;
20042
20068
  var CART_URL = `${AUCHAN_BASE}/on/demandware.store/Sites-AuchanPT-Site/pt_PT/Cart-Show`;
20069
+ var EMAIL_FIELD = 'input[name="uname1"], input[type="email"]';
20070
+ var PASSWORD_FIELD = 'input[name="passwordLogin"], input[type="password"]';
20071
+ var SUBMIT_BUTTON = '#btnSubmit_login, input[name="btnSubmit_login"], button[type="submit"]:visible';
20072
+ var CHALLENGE = 'iframe[src*="captcha"], iframe[title*="challenge"], .g-recaptcha, .h-captcha, #px-captcha';
20043
20073
  var BROWSER_CHANNELS = ["msedge", "chrome", void 0];
20044
20074
  var NO_PLAYWRIGHT = "falta o Playwright; instala o pacote de novo (npm install auchan-mcp)";
20045
- var NO_BROWSER = 'n\xE3o encontrei o Microsoft Edge nem o Google Chrome para abrir a janela de login; instala um deles ou corre "npx playwright install chromium"';
20075
+ var NO_BROWSER = 'n\xE3o encontrei o Microsoft Edge nem o Google Chrome para fazer o login; instala um deles ou corre "npx playwright install chromium"';
20076
+ var FORM_CHANGED = "o formul\xE1rio de login do Auchan n\xE3o \xE9 o esperado (o site pode ter mudado); \xE9 preciso atualizar o servidor";
20077
+ var LOGIN_FAILED = "o login n\xE3o terminou; confirma o email e a palavra-passe no ficheiro de credenciais";
20078
+ var NO_DELIVERY = "o Auchan ainda n\xE3o tem a tua entrega escolhida; escolhe-a uma vez no auchan.pt (entrega em casa ou levantamento, com o c\xF3digo postal) e tenta de novo";
20046
20079
  function writeState(path, state) {
20047
20080
  mkdirSync2(dirname(path), { recursive: true, mode: 448 });
20048
20081
  writeFileSync2(path, JSON.stringify(state, null, 2), { mode: 384 });
20049
20082
  chmodSync(path, 384);
20050
20083
  }
20051
20084
  function readState(path) {
20052
- if (!existsSync(path)) return null;
20085
+ if (!existsSync2(path)) return null;
20053
20086
  try {
20054
- const saved = JSON.parse(readFileSync3(path, "utf8"));
20087
+ const saved = JSON.parse(readFileSync4(path, "utf8"));
20055
20088
  const state = Array.isArray(saved) ? { cookies: saved, origins: [] } : { cookies: saved?.cookies, origins: saved?.origins ?? [] };
20056
20089
  return Array.isArray(state.cookies) && state.cookies.length ? state : null;
20057
20090
  } catch {
@@ -20059,13 +20092,14 @@ function readState(path) {
20059
20092
  }
20060
20093
  }
20061
20094
  function createSession({
20095
+ credentials,
20096
+ credentialFile = "credentials.env",
20062
20097
  cookieFile,
20063
20098
  log = console.error,
20064
20099
  loadPlaywright = () => import("playwright"),
20065
- loginTimeoutMs = 5 * 60 * 1e3,
20066
- keepAliveMs = 10 * 60 * 1e3,
20067
- pollMs = 3e3,
20068
- sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
20100
+ headless = true,
20101
+ loginTimeoutMs = 3e4,
20102
+ keepAliveMs = 10 * 60 * 1e3
20069
20103
  }) {
20070
20104
  let playwright = null;
20071
20105
  let api = null;
@@ -20105,7 +20139,7 @@ function createSession({
20105
20139
  assertNotBlocked(res.status(), "");
20106
20140
  return res.ok() && /data-has-context="true"/.test(await res.text());
20107
20141
  }
20108
- async function launch(chromium, headless) {
20142
+ async function launch(chromium) {
20109
20143
  for (const channel of BROWSER_CHANNELS) {
20110
20144
  try {
20111
20145
  return await chromium.launch({ headless, ...channel && { channel } });
@@ -20114,31 +20148,49 @@ function createSession({
20114
20148
  }
20115
20149
  return null;
20116
20150
  }
20117
- async function interactiveLogin(saved = null) {
20151
+ function scrubbed(error62) {
20152
+ for (const secret of [credentials?.email, credentials?.password]) {
20153
+ if (secret) error62.message = String(error62.message).split(secret).join("***");
20154
+ }
20155
+ return error62;
20156
+ }
20157
+ async function autoLogin() {
20158
+ if (!credentials) {
20159
+ throw new Error(`falta o ficheiro ${credentialFile} com as linhas AUCHAN_EMAIL=... e AUCHAN_PASSWORD=... (ver o README)`);
20160
+ }
20118
20161
  const { chromium } = await pw();
20119
- const browser = await launch(chromium, false);
20162
+ const browser = await launch(chromium);
20120
20163
  if (!browser) throw new Error(NO_BROWSER);
20121
- const closed = new Promise((resolve) => browser.on("disconnected", () => resolve("closed")));
20122
20164
  try {
20123
- const context = await browser.newContext({ locale: "pt-PT", ...saved && { storageState: saved } });
20165
+ const context = await browser.newContext({ locale: "pt-PT", userAgent: USER_AGENT });
20124
20166
  const page = await context.newPage();
20125
- log(saved ? "[auth] Abri uma janela do browser: escolhe a entrega ou o levantamento no Auchan." : "[auth] Abri uma janela do browser: faz o login no Auchan e escolhe a entrega ou o levantamento.");
20126
- await throttle(() => page.goto(saved ? `${AUCHAN_BASE}/pt/` : LOGIN_URL, { waitUntil: "domcontentloaded", timeout: 3e4 }));
20127
- const deadline = Date.now() + loginTimeoutMs;
20128
- while (Date.now() < deadline) {
20129
- if (await Promise.race([closed, sleep(pollMs).then(() => "tick")]) === "closed") {
20130
- throw new Error("a janela de login foi fechada antes de o login terminar; tenta de novo");
20131
- }
20132
- const url2 = new URL(page.url());
20133
- if (url2.hostname !== "www.auchan.pt" || /login/i.test(url2.pathname)) continue;
20134
- if (await isLoggedIn(context.request) && await hasDeliveryContext(context.request)) {
20135
- const next = await context.storageState();
20136
- writeState(cookieFile, next);
20137
- log("[auth] Sess\xE3o iniciada.");
20138
- return next;
20167
+ log("[auth] A iniciar sess\xE3o no Auchan...");
20168
+ await throttle(() => page.goto(LOGIN_URL, { waitUntil: "domcontentloaded", timeout: 3e4 }));
20169
+ await page.locator("#onetrust-accept-btn-handler").click({ timeout: 2e3 }).catch(() => {
20170
+ });
20171
+ try {
20172
+ await page.locator(EMAIL_FIELD).first().fill(credentials.email, { timeout: 2e4 });
20173
+ await page.locator(PASSWORD_FIELD).first().fill(credentials.password, { timeout: 1e4 });
20174
+ await throttle(() => page.locator(SUBMIT_BUTTON).first().click({ timeout: 1e4 }));
20175
+ } catch {
20176
+ throw new Error(FORM_CHANGED);
20177
+ }
20178
+ try {
20179
+ await page.waitForURL((url2) => url2.hostname === "www.auchan.pt" && !/login/i.test(url2.pathname), { timeout: loginTimeoutMs });
20180
+ } catch {
20181
+ if (await page.locator(CHALLENGE).count()) {
20182
+ throw new BlockedError("o site pediu uma verifica\xE7\xE3o anti-rob\xF4 (CAPTCHA) no login; entra uma vez no auchan.pt pelo browser e tenta mais tarde");
20139
20183
  }
20184
+ throw new Error(LOGIN_FAILED);
20140
20185
  }
20141
- throw new Error("o login n\xE3o terminou em 5 minutos; tenta de novo");
20186
+ if (!await isLoggedIn(context.request)) throw new Error(LOGIN_FAILED);
20187
+ const next = await context.storageState();
20188
+ writeState(cookieFile, next);
20189
+ log("[auth] Sess\xE3o iniciada.");
20190
+ return next;
20191
+ } catch (error62) {
20192
+ if (error62 instanceof BlockedError || error62.message === LOGIN_FAILED || error62.message === FORM_CHANGED || error62.message.startsWith("falta ")) throw scrubbed(error62);
20193
+ throw new Error(LOGIN_FAILED);
20142
20194
  } finally {
20143
20195
  await browser.close().catch(() => {
20144
20196
  });
@@ -20167,8 +20219,10 @@ function createSession({
20167
20219
  await persist(true);
20168
20220
  return;
20169
20221
  }
20170
- loggedIn = false;
20222
+ if (credentials) await openApi(await autoLogin());
20223
+ else loggedIn = false;
20171
20224
  } catch {
20225
+ loggedIn = false;
20172
20226
  } finally {
20173
20227
  beating = false;
20174
20228
  }
@@ -20179,26 +20233,19 @@ function createSession({
20179
20233
  if (api && loggedIn) return Promise.resolve("saved");
20180
20234
  pending ??= (async () => {
20181
20235
  try {
20236
+ let how = "login";
20182
20237
  const saved = readState(cookieFile);
20183
20238
  if (saved) {
20184
20239
  await openApi(saved);
20185
- const logged = await isLoggedIn(api);
20186
- if (logged && await hasDeliveryContext(api)) {
20187
- loggedIn = true;
20188
- await persist(true);
20189
- startKeepAlive();
20190
- return "saved";
20191
- }
20192
- log(logged ? "[auth] Falta escolher a entrega." : "[auth] A sess\xE3o guardada expirou.");
20193
- await openApi(await interactiveLogin(logged ? saved : null));
20194
- loggedIn = true;
20195
- startKeepAlive();
20196
- return "window";
20240
+ if (await isLoggedIn(api)) how = "saved";
20241
+ else log("[auth] A sess\xE3o guardada expirou; vou entrar outra vez.");
20197
20242
  }
20198
- await openApi(await interactiveLogin());
20243
+ if (how === "login") await openApi(await autoLogin());
20244
+ if (!await hasDeliveryContext(api)) throw new Error(NO_DELIVERY);
20199
20245
  loggedIn = true;
20246
+ await persist(true);
20200
20247
  startKeepAlive();
20201
- return "window";
20248
+ return how;
20202
20249
  } finally {
20203
20250
  pending = null;
20204
20251
  }
@@ -20302,12 +20349,12 @@ var CART_TOOLS = [
20302
20349
  var LOGIN_TOOLS = [
20303
20350
  {
20304
20351
  name: "login",
20305
- description: "Connect your Auchan account. Opens a browser window on the Auchan.pt login page; the person logs in there and chooses home delivery or pickup with a postal code (Auchan needs this before any cart change), then the window closes. Tell the person this before calling. The password is never seen or stored. Cart tools call this automatically when there is no session.",
20352
+ description: "Connect your Auchan account with the email and password in credentials.env (no window). Logs in again by itself whenever the session ends. Cart tools call this automatically.",
20306
20353
  inputSchema: { type: "object", properties: {} }
20307
20354
  },
20308
20355
  {
20309
20356
  name: "logout",
20310
- description: "Disconnect your Auchan account: deletes the saved session from this computer.",
20357
+ description: "Disconnect your Auchan account: deletes the saved session from this computer (credentials.env is kept).",
20311
20358
  inputSchema: { type: "object", properties: {} }
20312
20359
  }
20313
20360
  ];
@@ -20340,7 +20387,13 @@ var AuchanServer = class {
20340
20387
  const paths = resolveStatePaths(env);
20341
20388
  this.cacheDir = paths.cacheDir;
20342
20389
  this.fetchImpl = fetchImpl;
20343
- this.session = session ?? createSession({ cookieFile: paths.cookieFile });
20390
+ this.credentialsFound = Boolean(loadCredentials(env));
20391
+ this.session = session ?? createSession({
20392
+ credentials: loadCredentials(env),
20393
+ credentialFile: paths.sharedCredentialFile,
20394
+ cookieFile: paths.cookieFile,
20395
+ headless: env.AUCHAN_LOGIN_HEADLESS !== "false"
20396
+ });
20344
20397
  this.tools = [SEARCH_TOOL, ...CART_TOOLS, ...LOGIN_TOOLS, CLOSE_TOOL];
20345
20398
  this.server = new Server({ name: "auchan-mcp", version: VERSION }, { capabilities: { tools: {} } });
20346
20399
  this.server.onclose = () => {
@@ -20384,7 +20437,7 @@ var AuchanServer = class {
20384
20437
  );
20385
20438
  }
20386
20439
  case "login":
20387
- return text(await this.session.login() === "saved" ? "J\xE1 tens a sess\xE3o do Auchan iniciada." : "Sess\xE3o do Auchan iniciada. J\xE1 posso usar o teu carrinho.");
20440
+ return text(await this.session.login() === "saved" ? "J\xE1 tens a sess\xE3o do Auchan iniciada." : "Sess\xE3o do Auchan iniciada com o ficheiro de credenciais. J\xE1 posso usar o teu carrinho.");
20388
20441
  case "logout":
20389
20442
  await this.session.logout();
20390
20443
  return text("Sess\xE3o do Auchan terminada: apaguei a sess\xE3o guardada neste computador.");
@@ -20398,7 +20451,7 @@ var AuchanServer = class {
20398
20451
  }
20399
20452
  async start() {
20400
20453
  await this.server.connect(new StdioServerTransport());
20401
- console.error(`Auchan MCP v${VERSION} started`);
20454
+ console.error(`Auchan MCP v${VERSION} started${this.credentialsFound ? "" : " (search only until credentials.env exists)"}`);
20402
20455
  }
20403
20456
  };
20404
20457
  if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auchan-mcp",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Servidor MCP para o Auchan.pt: pesquisa de produtos e gestão do carrinho.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,6 +28,7 @@
28
28
  "license": "MIT",
29
29
  "files": [
30
30
  "dist/",
31
+ ".env.example",
31
32
  "LICENSE",
32
33
  "README.md"
33
34
  ],