migros-mcp 0.3.1 → 0.4.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.
Files changed (46) hide show
  1. package/README.md +28 -20
  2. package/dist/auth/api.js +18 -1
  3. package/dist/auth/api.js.map +1 -1
  4. package/dist/auth/cookies.d.ts +11 -0
  5. package/dist/auth/cookies.js +4 -0
  6. package/dist/auth/cookies.js.map +1 -1
  7. package/dist/auth/index.d.ts +20 -3
  8. package/dist/auth/index.js +75 -21
  9. package/dist/auth/index.js.map +1 -1
  10. package/dist/auth/login.d.ts +20 -2
  11. package/dist/auth/login.js +48 -5
  12. package/dist/auth/login.js.map +1 -1
  13. package/dist/auth/oauth.d.ts +11 -1
  14. package/dist/auth/oauth.js +41 -13
  15. package/dist/auth/oauth.js.map +1 -1
  16. package/dist/auth/rate-limit.d.ts +70 -0
  17. package/dist/auth/rate-limit.js +117 -0
  18. package/dist/auth/rate-limit.js.map +1 -0
  19. package/dist/auth-tools/_shared.d.ts +1 -1
  20. package/dist/auth-tools/_shared.js +2 -2
  21. package/dist/auth-tools/_shared.js.map +1 -1
  22. package/dist/auth-tools/authenticate.d.ts +10 -0
  23. package/dist/auth-tools/authenticate.js +17 -0
  24. package/dist/auth-tools/authenticate.js.map +1 -0
  25. package/dist/auth-tools/cart.d.ts +19 -1
  26. package/dist/auth-tools/cart.js +38 -4
  27. package/dist/auth-tools/cart.js.map +1 -1
  28. package/dist/auth-tools.d.ts +2 -1
  29. package/dist/auth-tools.js +2 -1
  30. package/dist/auth-tools.js.map +1 -1
  31. package/dist/index.d.ts +0 -0
  32. package/dist/index.js +7 -5
  33. package/dist/index.js.map +1 -1
  34. package/dist/migros.d.ts +0 -0
  35. package/dist/migros.js +0 -0
  36. package/dist/migros.js.map +0 -0
  37. package/dist/schemas.d.ts +14 -0
  38. package/dist/schemas.js +15 -2
  39. package/dist/schemas.js.map +1 -1
  40. package/dist/tools.d.ts +0 -0
  41. package/dist/tools.js +0 -0
  42. package/dist/tools.js.map +0 -0
  43. package/dist/types.d.ts +0 -0
  44. package/dist/types.js +0 -0
  45. package/dist/types.js.map +0 -0
  46. package/package.json +3 -1
package/README.md CHANGED
@@ -30,7 +30,7 @@ Search [Migros](https://www.migros.ch) products and (optionally) manage your bas
30
30
  - "Which Cumulus coupons do I have right now?"
31
31
  - "How many Cumulus points do I have?"
32
32
 
33
- The basket and order tools require your Migros account credentials. They're optional — if you skip them at install, the anonymous tools still work.
33
+ The basket and order tools require your Migros account credentials. They're optional. If you skip them at install, the anonymous tools still work.
34
34
 
35
35
  ## Install
36
36
 
@@ -39,9 +39,9 @@ The basket and order tools require your Migros account credentials. They're opti
39
39
  1. Download [migros-mcp.mcpb](https://github.com/lewpgs/migros-mcp/releases/latest/download/migros-mcp.mcpb)
40
40
  2. Install it:
41
41
 
42
- **macOS** — Double-click the file, or drag and drop it onto the Claude Desktop app icon
42
+ **macOS**: Double-click the file, or drag and drop it onto the Claude Desktop app icon
43
43
 
44
- **Windows** — In Claude Desktop, go to File > Settings > Extensions > Advanced Settings > Install Extension and select the file
44
+ **Windows**: In Claude Desktop, go to File > Settings > Extensions > Advanced Settings > Install Extension and select the file
45
45
 
46
46
  3. Optional: enter your Migros email, password, and TOTP secret if you want the basket/order tools. Leave all three blank for anonymous access only. Credentials are stored in your OS keychain by Claude Desktop.
47
47
 
@@ -49,13 +49,15 @@ The basket and order tools require your Migros account credentials. They're opti
49
49
 
50
50
  How to find it:
51
51
 
52
- - **If you saved it during enrollment** — copy it from your password manager. In 1Password / Bitwarden / Proton Pass, open the Migros entry, edit the OTP field, choose *"View one-time password secret"* (or similar). The string after `secret=` is what you want.
53
- - **If you didn't save it** — Migros only shows the seed during initial 2FA setup, on the same screen as the QR code, in a box labeled *"Or enter the following key in the authenticator app:"*. Once enrollment completes, the seed is gone for good.
52
+ - **If you saved it during enrollment**: copy it from your password manager. In 1Password / Bitwarden / Proton Pass, open the Migros entry, edit the OTP field, choose *"View one-time password secret"* (or similar). The string after `secret=` is what you want.
53
+ - **If you didn't save it**: Migros only shows the seed during initial 2FA setup, on the same screen as the QR code, in a box labeled *"Or enter the following key in the authenticator app:"*. Once enrollment completes, the seed is gone for good.
54
54
 
55
- To recover it: in Migros account settings, **remove** your existing 2FA, then **set it up again**. The new setup screen will show the new seed text below the QR code — copy it before completing enrollment, and re-add the new entry to your authenticator app.
55
+ To recover it: in Migros account settings, **remove** your existing 2FA, then **set it up again**. The new setup screen will show the new seed text below the QR code. Copy it before completing enrollment, and re-add the new entry to your authenticator app.
56
56
 
57
57
  Leave the field blank entirely if your Migros account doesn't have 2FA enabled.
58
58
 
59
+ **Rather not store the seed?** Leave the TOTP field blank. When Claude needs to log in, it asks you for the current 6-digit code from your authenticator app (or reads it from a password manager it has access to) and passes it to the `authenticate` tool. The code is used for that one login and is not stored.
60
+
59
61
  That's it.
60
62
 
61
63
  ---
@@ -82,8 +84,8 @@ claude mcp add migros \
82
84
 
83
85
  Add to your config:
84
86
 
85
- - **Claude Desktop** — `claude_desktop_config.json`
86
- - **Cursor** — `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global)
87
+ - **Claude Desktop**: `claude_desktop_config.json`
88
+ - **Cursor**: `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global)
87
89
 
88
90
  ```json
89
91
  {
@@ -101,7 +103,7 @@ Add to your config:
101
103
  }
102
104
  ```
103
105
 
104
- The `env` block is optional. Omit it to use anonymous tools only.
106
+ The `env` block is optional. Omit it to use anonymous tools only. Leave out `MIGROS_TOTP_SECRET` if you'd rather give Claude the current 6-digit code when it needs to log in (see `authenticate` below).
105
107
 
106
108
  ### Test connection
107
109
 
@@ -112,7 +114,7 @@ MIGROS_EMAIL=... MIGROS_PASSWORD=... MIGROS_TOTP_SECRET=... npx migros-mcp # a
112
114
 
113
115
  ## Updating
114
116
 
115
- The recommended install commands above use `npx -y migros-mcp` without pinning a version, so you auto-receive new releases. `npx` caches the package for ~7 days, so updates land on next launch within that window. To force-refresh sooner: `npm cache clean --force` and restart the host.
117
+ The recommended install commands above use `npx -y migros-mcp` without pinning a version, so you auto-receive new releases. Recent npm versions check for the newest version each time `npx` starts, so an update lands the next time your MCP host starts the server. If it seems stuck on an old version, run `npm cache clean --force` and restart the host.
116
118
 
117
119
  If you've installed via the **drag-installed `.mcpb`** instead, Claude Desktop pins you to that specific version. Download the latest `.mcpb` from [Releases](https://github.com/lewpgs/migros-mcp/releases) and re-install to update. Your credentials in the OS keychain are preserved across re-installs.
118
120
 
@@ -132,14 +134,16 @@ If you've **explicitly pinned** a version in your config (e.g. `migros-mcp@0.3.0
132
134
  | `get_promotions` | Search current Migros promotions and deals. |
133
135
  | `search_recipes` | Search Migusto (Migros' recipe site) by query and/or ingredients. German/French/Italian only. |
134
136
  | `get_recipe_details` | Full recipe by slug: ingredients, instructions, photos, nutrition. |
135
- | `get_recipe_products` | Migros product UIDs for a recipe — feed directly into `add_to_basket`. |
137
+ | `get_recipe_products` | Migros product UIDs for a recipe, ready to pass straight to `add_to_basket`. |
136
138
 
137
139
  ### Authenticated (require credentials)
138
140
 
139
141
  | Tool | Description |
140
142
  |---|---|
143
+ | `authenticate` | Log in with the current six-digit 2FA code. Only needed when no TOTP secret is set. Reuses a cached session when possible. |
141
144
  | `get_profile` | Logged-in customer's basic profile (name, email, language). |
142
145
  | `get_addresses` | Saved delivery and billing addresses. |
146
+ | `get_shopping_lists` | The user's shopping lists by name, for people with more than one. Pass a list's id to the basket tools. |
143
147
  | `get_basket` | Items currently in the user's basket. |
144
148
  | `add_to_basket` | Add a product (sets target quantity, default 1). |
145
149
  | `update_basket_quantity` | Set an item's exact quantity (0 to remove). |
@@ -158,7 +162,9 @@ If you've **explicitly pinned** a version in your config (e.g. `migros-mcp@0.3.0
158
162
  Use the [migros-api-wrapper](https://www.npmjs.com/package/migros-api-wrapper) which calls the same endpoints the Migros website uses internally. A guest token is fetched at startup and cached.
159
163
 
160
164
  ### Authenticated tools
161
- Use OAuth 2.0 against `login.migros.ch`. The first call runs a credentialed login (email → password → TOTP) and caches the resulting cookies + access token. Every 30 minutes the token is refreshed silently using cached cookies — no login form re-submission.
165
+ Use OAuth 2.0 against `login.migros.ch`. The first call runs a credentialed login (email → password → TOTP) and caches the resulting cookies + access token. Every 30 minutes the token is refreshed silently using cached cookies, without submitting the login form again.
166
+
167
+ With `MIGROS_TOTP_SECRET` set, the MCP makes the 2FA code itself. Without it, the AI passes the current code to `authenticate`: it asks the user, or reads it from a password manager it has access to (for example a 1Password MCP or the 1Password CLI). The code is used for that one login and is never stored or logged.
162
168
 
163
169
  The cache is persisted across MCP process restarts (Claude Desktop launches, Mac reboots, etc.) so we re-authenticate maybe twice a year instead of dozens of times a day. Migros' login endpoint is behind Cloudflare bot detection that throttles frequent logins, so persistence is what keeps the MCP from getting rate-limited.
164
170
 
@@ -170,14 +176,16 @@ The session lives at:
170
176
 
171
177
  If the cached session expires (typically a few weeks), the MCP automatically re-runs the credentialed login.
172
178
 
179
+ Without a stored TOTP secret, authenticated tools return `fresh_code_required` instead, and the AI calls `authenticate` with a new code. The MCP remembers that the account needs a code, so it doesn't send your password to Migros until it has one.
180
+
173
181
  ### Order placement
174
182
  For safety, this MCP **does not** place orders programmatically. The `get_checkout_link` tool returns a URL the user opens in their real browser to confirm address, delivery slot, and payment via Migros' own checkout flow. Real money requires a real human click.
175
183
 
176
184
  ## Known limitations
177
185
 
178
- - **2FA support is TOTP-only.** Accounts secured with a passkey (and no TOTP fallback) are not supported in v0.3.0. If your Migros account uses passkey as the only second factor, add a TOTP authenticator app in Migros account settings, then provide the TOTP secret to this MCP.
179
- - **No automatic order placement.** See above — `get_checkout_link` hands off to your browser for the actual placement.
180
- - **Cloudflare rate limits.** If the MCP fails the credentialed login repeatedly in a short window (e.g., wrong password retries), Cloudflare may briefly throttle the IP. Wait an hour and retry, or log in via your browser to refresh the session.
186
+ - **2FA support is TOTP-only.** Accounts secured with a passkey (and no TOTP fallback) are not supported. If your Migros account uses passkey as the only second factor, add a TOTP authenticator app in Migros account settings, then provide the TOTP secret to this MCP, or pass the current code to `authenticate`.
187
+ - **No automatic order placement.** See above: `get_checkout_link` hands off to your browser for the actual placement.
188
+ - **Cloudflare rate limits.** The login host (`login.migros.ch`) sits behind Cloudflare bot detection. If the MCP triggers too many login attempts in quick succession (wrong password retries, repeated restarts without a cached session), the IP may be throttled. When this happens the MCP now sets a **cooldown** (respecting `Retry-After` headers or falling back to 30 s → 2 min → 10 min backoff) and **stops hitting the login host** until it clears. During cooldown, any **still-valid cached JWT keeps working** so your MCP tools may continue serving requests. Only re-authentication is paused. If the cached JWT is also expired while rate-limited the MCP will surface a clear error with the cooldown deadline rather than silently retrying and worsening the throttle.
181
189
 
182
190
  ## Development
183
191
 
@@ -197,11 +205,11 @@ npx -y @modelcontextprotocol/inspector npx migros-mcp
197
205
 
198
206
  ## Disclaimers
199
207
 
200
- - **Unofficial** — Not affiliated with, endorsed by, or connected to Migros in any way.
201
- - **No official API** — Uses endpoints that power the Migros website. They are not documented or supported. Migros can change them without notice.
202
- - **Credentials** — When you provide credentials, they're used only to authenticate against `login.migros.ch`. Resulting session cookies and access tokens are stored locally in your OS user config directory. They are never sent anywhere except Migros' own servers.
203
- - **No order placement** — This server does not place orders programmatically. Orders are completed in the user's own browser via the URL returned by `get_checkout_link`.
204
- - **Use at your own risk** — No guarantees of functionality, availability, or compatibility.
208
+ - **Unofficial**: Not affiliated with, endorsed by, or connected to Migros in any way.
209
+ - **No official API**: Uses endpoints that power the Migros website. They are not documented or supported. Migros can change them without notice.
210
+ - **Credentials**: When you provide credentials, they're used only to authenticate against `login.migros.ch`. A 6-digit code passed to `authenticate` is used for that one login and is never stored or logged. Resulting session cookies and access tokens are stored locally in your OS user config directory. They are never sent anywhere except Migros' own servers.
211
+ - **No order placement**: This server does not place orders programmatically. Orders are completed in the user's own browser via the URL returned by `get_checkout_link`.
212
+ - **Use at your own risk**: No guarantees of functionality, availability, or compatibility.
205
213
 
206
214
  ## License
207
215
 
package/dist/auth/api.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { client, silentOAuth, uniformHeaders } from "./oauth.js";
2
2
  import { ingestSetCookie, loadSession, saveSession } from "./cookies.js";
3
3
  import { getJwt } from "./index.js";
4
+ import { isRateLimited, RateLimitError } from "./rate-limit.js";
4
5
  const BASE = "https://www.migros.ch";
5
6
  async function makeRequest(method, path, body, jwt, language, session) {
6
7
  const headers = uniformHeaders("www.migros.ch", session, {
@@ -41,8 +42,17 @@ export async function api(method, path, body, opts = {}) {
41
42
  saveSession(session);
42
43
  // Token rejected? Force a silent refresh and retry once.
43
44
  if (r.status === 401) {
45
+ session = loadSession();
46
+ // Never hammer the rate-limited host: if a cooldown is active, skip the
47
+ // refresh entirely and surface a clear error instead of firing another
48
+ // login-host request (which would 429 and extend the throttle).
49
+ if (isRateLimited(session)) {
50
+ const until = session.rateLimitedUntil
51
+ ? new Date(session.rateLimitedUntil).toISOString()
52
+ : "unknown time";
53
+ throw new Error(`${method} ${path} -> 401 (token rejected, but login.migros.ch is rate-limited until ${until} — cannot refresh)`);
54
+ }
44
55
  try {
45
- session = loadSession();
46
56
  jwt = await silentOAuth(session);
47
57
  saveSession(session);
48
58
  r = await makeRequest(method, path, body, jwt, language, session);
@@ -50,6 +60,13 @@ export async function api(method, path, body, opts = {}) {
50
60
  saveSession(session);
51
61
  }
52
62
  catch (e) {
63
+ // Rate-limit errors should always surface — swallowing them hides the
64
+ // cooldown from the caller and defeats the cascade-prevention logic.
65
+ if (e instanceof RateLimitError) {
66
+ // Save the cooldown silentOAuth just set, so the next call doesn't retry.
67
+ saveSession(session);
68
+ throw e;
69
+ }
53
70
  // Silent refresh failed (cookies actually expired). Surface the original
54
71
  // 401 since that's more actionable than the silent-refresh error.
55
72
  }
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/auth/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/auth/api.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,17 @@ export interface Session {
11
11
  cookies: Record<string, Record<string, string>>;
12
12
  jwt: string | null;
13
13
  jwtExp: number;
14
+ /**
15
+ * Unix epoch **milliseconds** when the login.migros.ch rate-limit cooldown
16
+ * ends. Absent or 0 means the host is not known to be rate-limiting.
17
+ */
18
+ rateLimitedUntil?: number;
19
+ /** Human-readable reason for the cooldown (carried from the 429 response). */
20
+ rateLimitReason?: string;
21
+ /** Consecutive 429s so far, used to step up the fallback backoff. */
22
+ rateLimitAttempts?: number;
23
+ /** The last full login hit a 2FA step. Not a secret, just a yes/no. */
24
+ twoFactorRequired?: boolean;
14
25
  }
15
26
  /** Read the persisted session, or return an empty one if no file exists. */
16
27
  export declare function loadSession(): Session;
@@ -10,6 +10,10 @@ export function loadSession() {
10
10
  cookies: parsed.cookies ?? {},
11
11
  jwt: parsed.jwt ?? null,
12
12
  jwtExp: parsed.jwtExp ?? 0,
13
+ rateLimitedUntil: parsed.rateLimitedUntil,
14
+ rateLimitReason: parsed.rateLimitReason,
15
+ rateLimitAttempts: parsed.rateLimitAttempts,
16
+ twoFactorRequired: parsed.twoFactorRequired,
13
17
  };
14
18
  }
15
19
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../src/auth/cookies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAiBpD,MAAM,KAAK,GAAY,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;AAE7D,4EAA4E;AAC5E,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,EAAE,MAAM,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAqB,CAAC;QACnD,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE;YAC7B,GAAG,EAAE,MAAM,CAAC,GAAG,IAAI,IAAI;YACvB,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC;SAC3B,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,uDAAuD;QACvD,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QACvF,0FAA0F;QAC1F,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACnC,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;IACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,WAAW,EAAE,CAAC;IAChC,MAAM,OAAO,GAAG,GAAG,SAAS,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;IAClD,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7E,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAgB,EAChB,IAAY,EACZ,UAAyC;IAEzC,IAAI,CAAC,UAAU;QAAE,OAAO;IACxB,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IACnE,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IAC3C,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;QACtB,yEAAyE;QACzE,2EAA2E;QAC3E,oEAAoE;QACpE,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAClD,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,EAAE,GAAG,CAAC;YAAE,SAAS;QACrB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACtC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACpB,CAAC;AACH,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,YAAY,CAAC,OAAgB,EAAE,IAAY;IACzD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;SACvB,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;SAC5B,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../src/auth/cookies.ts"],"names":[],"mappings":"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"}
@@ -5,13 +5,30 @@ export type { Session, Credentials };
5
5
  export { silentOAuth, fullLogin };
6
6
  /**
7
7
  * Returns a usable access token, doing whatever is needed:
8
- * 1. Use the cached JWT if it's still valid (with a 60s buffer).
8
+ * 1. Use the cached JWT if it's still valid (with a 5 min buffer).
9
9
  * 2. Else run silent OAuth using cached SSO cookies.
10
- * 3. Else fall back to a full credentialed login (email -> password -> TOTP).
10
+ * 3. Else fall back to a full credentialed login (email → password → TOTP).
11
+ *
12
+ * **Rate-limit handling (429 Too Many Requests):**
13
+ *
14
+ * - If the session carries an active rate-limit cooldown (`rateLimitedUntil` in
15
+ * the future), we **never** attempt silent OAuth or full login. Instead we
16
+ * fail‑open when the cached JWT is still valid or throw a clear error.
17
+ * - A 429 from silent OAuth is **not** cascaded into full login because they
18
+ * share the same host (`login.migros.ch`) and the same Cloudflare throttle.
19
+ * - After a successful refresh the cooldown is cleared automatically.
11
20
  *
12
21
  * Persists the session to disk after any refresh so subsequent calls are fast.
13
22
  *
14
23
  * Throws if `creds` is missing AND no cookies are cached (first-time install
15
24
  * with no env vars set).
16
25
  */
17
- export declare function getJwt(creds?: Credentials): Promise<string>;
26
+ export interface GetJwtOptions {
27
+ /**
28
+ * Try a full login even when the account is known to need a 2FA code and no
29
+ * seed or code was given. Set by the `authenticate` tool, so an account that
30
+ * has since turned 2FA off can still log in.
31
+ */
32
+ tryLoginWithoutCode?: boolean;
33
+ }
34
+ export declare function getJwt(creds?: Credentials, opts?: GetJwtOptions): Promise<string>;
@@ -1,36 +1,90 @@
1
1
  import { loadSession, saveSession } from "./cookies.js";
2
- import { getJwt as getJwtFromOauth, silentOAuth } from "./oauth.js";
3
- import { fullLogin } from "./login.js";
2
+ import { getJwt as getJwtFromOauth, silentOAuth, JWT_BUFFER_SECONDS } from "./oauth.js";
3
+ import { fullLogin, FreshCodeRequiredError } from "./login.js";
4
+ import { isRateLimited, RateLimitError, setRateLimited, clearRateLimit, rateLimitBlockedMessage, } from "./rate-limit.js";
4
5
  export { silentOAuth, fullLogin };
5
- /**
6
- * Returns a usable access token, doing whatever is needed:
7
- * 1. Use the cached JWT if it's still valid (with a 60s buffer).
8
- * 2. Else run silent OAuth using cached SSO cookies.
9
- * 3. Else fall back to a full credentialed login (email -> password -> TOTP).
10
- *
11
- * Persists the session to disk after any refresh so subsequent calls are fast.
12
- *
13
- * Throws if `creds` is missing AND no cookies are cached (first-time install
14
- * with no env vars set).
15
- */
16
- export async function getJwt(creds) {
6
+ export async function getJwt(creds, opts = {}) {
17
7
  const session = loadSession();
18
- // Path 1+2: try cached JWT, fall through to silent OAuth.
8
+ // --- Rate-limit cooldown guard ---
9
+ if (isRateLimited(session)) {
10
+ // Fail‑open: if the cached JWT is still valid we return it regardless of
11
+ // the throttle — a working session must never be blocked by the login rate
12
+ // limiter. Apply the same buffer as the refresh path so we never hand a
13
+ // near-expired token to the API (it would 401 and trigger a rate-limited
14
+ // refresh attempt).
15
+ const now = Math.floor(Date.now() / 1000);
16
+ if (session.jwt && session.jwtExp && session.jwtExp - now > JWT_BUFFER_SECONDS) {
17
+ return session.jwt;
18
+ }
19
+ // JWT expired + rate‑limited → hard fail with an actionable message.
20
+ throw new Error(rateLimitBlockedMessage(session));
21
+ }
22
+ // --- Path 1+2: try cached JWT, fall through to silent OAuth ---
19
23
  try {
20
24
  const jwt = await getJwtFromOauth(session);
25
+ // Successful refresh clears any stale cooldown.
26
+ clearRateLimit(session);
21
27
  saveSession(session);
22
28
  return jwt;
23
29
  }
24
30
  catch (e) {
25
- // Silent OAuth failed (no cookies / expired cookies). Fall through.
31
+ if (e instanceof RateLimitError) {
32
+ // 429 from silent OAuth → apply cooldown, persist, and do NOT cascade.
33
+ // The throwing call already set the cooldown. Only set it here if it didn't,
34
+ // so one 429 isn't counted twice by the backoff.
35
+ if (!isRateLimited(session))
36
+ setRateLimited(session, e.retryAfterSeconds, e.message);
37
+ saveSession(session);
38
+ // Fail‑open: if the current (possibly stale) JWT is still valid we
39
+ // return it rather than failing the tool call entirely. Apply the same
40
+ // buffer as the refresh path (see above).
41
+ const now = Math.floor(Date.now() / 1000);
42
+ if (session.jwt && session.jwtExp && session.jwtExp - now > JWT_BUFFER_SECONDS) {
43
+ return session.jwt;
44
+ }
45
+ // No usable JWT — surface the rate-limit error.
46
+ throw e;
47
+ }
48
+ // Not a rate-limit error — silent OAuth failed because cookies are
49
+ // missing/expired. Fall through to a full credentialed login.
26
50
  if (!creds) {
27
51
  throw new Error("no cached session and no credentials provided. " +
28
- "Set MIGROS_EMAIL, MIGROS_PASSWORD, and MIGROS_TOTP_SECRET env vars to enable login.");
52
+ "Set MIGROS_EMAIL and MIGROS_PASSWORD to enable login. If the account has 2FA, also set " +
53
+ "MIGROS_TOTP_SECRET, or call authenticate with the current six-digit code.");
54
+ }
55
+ }
56
+ // Don't start a login that can't finish: if the last login needed a 2FA code
57
+ // and there is neither a stored seed nor a code, ask for the code before
58
+ // sending the password to Migros. Repeated half-logins get the IP blocked.
59
+ if (session.twoFactorRequired && !creds.totpSecret && !creds.totpCode && !opts.tryLoginWithoutCode) {
60
+ throw new FreshCodeRequiredError();
61
+ }
62
+ // --- Path 3: full credentialed login ---
63
+ try {
64
+ const jwt = await fullLogin(session, creds);
65
+ // Successful login clears any stale cooldown.
66
+ clearRateLimit(session);
67
+ saveSession(session);
68
+ return jwt;
69
+ }
70
+ catch (e) {
71
+ // If fullLogin hits a 429, persist the cooldown so the next call doesn't
72
+ // immediately retry.
73
+ if (e instanceof RateLimitError) {
74
+ // The throwing call already set the cooldown. Only set it here if it didn't,
75
+ // so one 429 isn't counted twice by the backoff.
76
+ if (!isRateLimited(session))
77
+ setRateLimited(session, e.retryAfterSeconds, e.message);
78
+ saveSession(session);
79
+ }
80
+ if (e instanceof FreshCodeRequiredError) {
81
+ // Remember that this account needs a code. Start from the saved session
82
+ // so the half-finished login's cookies aren't kept.
83
+ const saved = loadSession();
84
+ saved.twoFactorRequired = true;
85
+ saveSession(saved);
29
86
  }
87
+ throw e;
30
88
  }
31
- // Path 3: full credentialed login.
32
- const jwt = await fullLogin(session, creds);
33
- saveSession(session);
34
- return jwt;
35
89
  }
36
90
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAgB,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,EAAE,SAAS,EAAoB,MAAM,YAAY,CAAC;AAGzD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;AAElC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,KAAmB;IAC9C,MAAM,OAAO,GAAY,WAAW,EAAE,CAAC;IAEvC,0DAA0D;IAC1D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC;QAC3C,WAAW,CAAC,OAAO,CAAC,CAAC;QACrB,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,oEAAoE;QACpE,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CACb,iDAAiD;gBAC/C,qFAAqF,CACxF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,mCAAmC;IACnC,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC5C,WAAW,CAAC,OAAO,CAAC,CAAC;IACrB,OAAO,GAAG,CAAC;AACb,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"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"}
@@ -2,14 +2,32 @@ import type { Session } from "./cookies.js";
2
2
  export interface Credentials {
3
3
  email: string;
4
4
  password: string;
5
- /** Base32-encoded TOTP seed. Required if 2FA is enabled on the account. */
5
+ /**
6
+ * Base32-encoded TOTP seed (MIGROS_TOTP_SECRET). When set, the MCP makes the
7
+ * 2FA code itself and re-logins need no help.
8
+ */
6
9
  totpSecret?: string;
10
+ /**
11
+ * Current six-digit TOTP code passed to the `authenticate` tool. The option
12
+ * for people who don't want to store the seed. Used before the seed if both
13
+ * are present.
14
+ */
15
+ totpCode?: string;
16
+ }
17
+ export declare class FreshCodeRequiredError extends Error {
18
+ constructor(message?: string);
7
19
  }
8
20
  /**
9
21
  * RFC 6238 TOTP code from a base32-encoded shared secret.
10
22
  * Defaults match Google Authenticator / 1Password / Authy: SHA-1, 30s, 6 digits.
11
23
  */
12
- export declare function totpCode(secretBase32: string, time?: number, step?: number, digits?: number): string;
24
+ export declare function totpFromSecret(secretBase32: string, time?: number, step?: number, digits?: number): string;
25
+ /**
26
+ * The code to submit at the 2FA step: the one passed to `authenticate` if
27
+ * there is one, else one made from the stored seed. Throws
28
+ * FreshCodeRequiredError when there is neither.
29
+ */
30
+ export declare function otpCodeFor(creds: Credentials, time?: number): string;
13
31
  /**
14
32
  * Run the full credentialed login (email → password → TOTP) and finish by
15
33
  * minting a JWT via silent OAuth. Mutates `session` in place. Caller persists.
@@ -1,12 +1,20 @@
1
1
  import crypto from "node:crypto";
2
2
  import { ingestSetCookie } from "./cookies.js";
3
3
  import { client, uniformHeaders, silentOAuth } from "./oauth.js";
4
+ import { parseRetryAfter, RateLimitError, setRateLimited } from "./rate-limit.js";
4
5
  const LOGIN = "https://login.migros.ch";
6
+ export class FreshCodeRequiredError extends Error {
7
+ constructor(message = "fresh_code_required: this Migros account needs a 2FA code. Ask the user for the current " +
8
+ "six-digit code from their authenticator app and call the authenticate tool with it.") {
9
+ super(message);
10
+ this.name = "FreshCodeRequiredError";
11
+ }
12
+ }
5
13
  /**
6
14
  * RFC 6238 TOTP code from a base32-encoded shared secret.
7
15
  * Defaults match Google Authenticator / 1Password / Authy: SHA-1, 30s, 6 digits.
8
16
  */
9
- export function totpCode(secretBase32, time = Math.floor(Date.now() / 1000), step = 30, digits = 6) {
17
+ export function totpFromSecret(secretBase32, time = Math.floor(Date.now() / 1000), step = 30, digits = 6) {
10
18
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
11
19
  const cleaned = secretBase32.replace(/=+$/g, "").replace(/\s+/g, "").toUpperCase();
12
20
  let bits = "";
@@ -27,6 +35,21 @@ export function totpCode(secretBase32, time = Math.floor(Date.now() / 1000), ste
27
35
  const slice = hmac.readUInt32BE(offset) & 0x7fffffff;
28
36
  return String(slice % 10 ** digits).padStart(digits, "0");
29
37
  }
38
+ /**
39
+ * The code to submit at the 2FA step: the one passed to `authenticate` if
40
+ * there is one, else one made from the stored seed. Throws
41
+ * FreshCodeRequiredError when there is neither.
42
+ */
43
+ export function otpCodeFor(creds, time) {
44
+ if (creds.totpCode) {
45
+ if (!/^\d{6}$/.test(creds.totpCode))
46
+ throw new Error("totpCode must be exactly six digits");
47
+ return creds.totpCode;
48
+ }
49
+ if (creds.totpSecret)
50
+ return totpFromSecret(creds.totpSecret, time);
51
+ throw new FreshCodeRequiredError();
52
+ }
30
53
  function findCsrf(html) {
31
54
  const m = html.match(/name="_csrf"[^>]*value="([^"]+)"/);
32
55
  if (!m)
@@ -42,6 +65,12 @@ async function get(url, session, extra = {}) {
42
65
  const host = new URL(url).host;
43
66
  const r = await client().get(url, { headers: uniformHeaders(host, session, { Accept: "text/html", ...extra }) });
44
67
  ingestSetCookie(session, host, r.headers["set-cookie"]);
68
+ if (r.status === 429) {
69
+ const retryAfter = parseRetryAfter(r.headers["retry-after"]);
70
+ setRateLimited(session, retryAfter, "login " + url + " -> 429");
71
+ throw new RateLimitError("login.migros.ch rate-limited (429) during GET " + url + ". " +
72
+ "Retry after: " + (retryAfter ?? "unknown") + "s", retryAfter);
73
+ }
45
74
  return r;
46
75
  }
47
76
  async function postForm(url, session, body) {
@@ -58,6 +87,12 @@ async function postForm(url, session, body) {
58
87
  }),
59
88
  });
60
89
  ingestSetCookie(session, host, r.headers["set-cookie"]);
90
+ if (r.status === 429) {
91
+ const retryAfter = parseRetryAfter(r.headers["retry-after"]);
92
+ setRateLimited(session, retryAfter, "login POST " + url + " -> 429");
93
+ throw new RateLimitError("login.migros.ch rate-limited (429) during POST " + url + ". " +
94
+ "Retry after: " + (retryAfter ?? "unknown") + "s", retryAfter);
95
+ }
61
96
  return r;
62
97
  }
63
98
  function locationOf(r, base) {
@@ -109,17 +144,25 @@ export async function fullLogin(session, creds) {
109
144
  nextUrl = `${LOGIN}/login/authenticator`;
110
145
  // 7. If a 2FA step is in the chain, handle it. Otherwise skip.
111
146
  const needsOtp = /\/login\/(authenticator|second|totp|otp)/.test(nextUrl);
147
+ // Remembered in the session so a later login without seed or code can stop
148
+ // before sending the password (see getJwt).
149
+ session.twoFactorRequired = needsOtp;
112
150
  if (needsOtp) {
113
- if (!creds.totpSecret)
114
- throw new Error("server requires 2FA but no TOTP secret was provided");
151
+ const code = otpCodeFor(creds);
115
152
  r = await get(nextUrl, session);
116
153
  csrf = findCsrf(r.data);
117
154
  const otpAction = r.data.match(/<form[^>]+action="([^"]*)"/)?.[1] || nextUrl;
118
155
  const otpUrl = new URL(otpAction, nextUrl).toString();
119
- r = await postForm(otpUrl, session, { _csrf: csrf, code: totpCode(creds.totpSecret) });
156
+ r = await postForm(otpUrl, session, { _csrf: csrf, code });
120
157
  nextUrl = locationOf(r, LOGIN);
121
158
  if (!nextUrl) {
122
- throw new Error(`OTP step failed: ${findError(r.data) ?? `status ${r.status}`}`);
159
+ const why = findError(r.data) ?? `status ${r.status}`;
160
+ if (creds.totpCode) {
161
+ // A code typed in or read from a password manager may simply have
162
+ // expired: ask for a new one, and keep the real reason in the message.
163
+ throw new FreshCodeRequiredError(`fresh_code_required: Migros did not accept the code (${why}). Get a new code and call authenticate once more.`);
164
+ }
165
+ throw new Error(`OTP step failed: ${why}`);
123
166
  }
124
167
  }
125
168
  // 8. Follow the post-login redirect chain to settle SSO state.
@@ -1 +1 @@
1
- {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/auth/login.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/auth/login.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,6 @@
1
1
  import type { AxiosInstance } from "axios";
2
2
  import type { Session } from "./cookies.js";
3
3
  export declare function client(): AxiosInstance;
4
- export declare function uaFor(host: string): string;
5
4
  export declare function uniformHeaders(host: string, session: Session, extra?: Record<string, string>): Record<string, string>;
6
5
  /**
7
6
  * Run the silent OAuth flow against current SSO cookies and return a fresh
@@ -10,9 +9,20 @@ export declare function uniformHeaders(host: string, session: Session, extra?: R
10
9
  * Throws if the SSO cookies are missing/expired (e.g., user logged out of
11
10
  * migros.ch in their browser, or session.json is stale by weeks).
12
11
  *
12
+ * On a 429 (rate-limit), sets `session.rateLimitedUntil` and throws a
13
+ * {@link RateLimitError} so the caller can surface the cooldown and avoid
14
+ * cascading into a full credentialed login.
15
+ *
13
16
  * Caller is responsible for persisting the session afterward.
14
17
  */
15
18
  export declare function silentOAuth(session: Session): Promise<string>;
19
+ /**
20
+ * How much JWT lifetime (in seconds) we keep in reserve before proactively
21
+ * refreshing via silent OAuth. Also used by fail-open paths in getJwt() so a
22
+ * near-expired token is never handed to the API (which would 401 and trigger
23
+ * a rate-limited refresh attempt).
24
+ */
25
+ export declare const JWT_BUFFER_SECONDS = 300;
16
26
  /**
17
27
  * Returns a usable JWT, refreshing via silent OAuth if the cached one is
18
28
  * within `bufferSeconds` of expiring (or missing). Mutates `session`.