albert-heijn-mcp 1.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/LICENSE +201 -0
- package/README.md +376 -0
- package/assets/icon.png +0 -0
- package/deploy/albert-heijn-mcp.service +42 -0
- package/dist/ahapi/auth.js +18 -0
- package/dist/ahapi/auth.js.map +1 -0
- package/dist/ahapi/bonus.js +53 -0
- package/dist/ahapi/bonus.js.map +1 -0
- package/dist/ahapi/client.js +115 -0
- package/dist/ahapi/client.js.map +1 -0
- package/dist/ahapi/favorites.js +67 -0
- package/dist/ahapi/favorites.js.map +1 -0
- package/dist/ahapi/index.js +12 -0
- package/dist/ahapi/index.js.map +1 -0
- package/dist/ahapi/member.js +23 -0
- package/dist/ahapi/member.js.map +1 -0
- package/dist/ahapi/orders.js +85 -0
- package/dist/ahapi/orders.js.map +1 -0
- package/dist/ahapi/products.js +103 -0
- package/dist/ahapi/products.js.map +1 -0
- package/dist/ahapi/receipts.js +37 -0
- package/dist/ahapi/receipts.js.map +1 -0
- package/dist/ahapi/recipes.js +57 -0
- package/dist/ahapi/recipes.js.map +1 -0
- package/dist/ahapi/shoppingList.js +58 -0
- package/dist/ahapi/shoppingList.js.map +1 -0
- package/dist/ahapi/stores.js +35 -0
- package/dist/ahapi/stores.js.map +1 -0
- package/dist/auth/login.js +15 -0
- package/dist/auth/login.js.map +1 -0
- package/dist/auth/session.js +125 -0
- package/dist/auth/session.js.map +1 -0
- package/dist/auth/tokens.js +132 -0
- package/dist/auth/tokens.js.map +1 -0
- package/dist/config.js +61 -0
- package/dist/config.js.map +1 -0
- package/dist/index.js +70 -0
- package/dist/index.js.map +1 -0
- package/dist/log.js +70 -0
- package/dist/log.js.map +1 -0
- package/dist/server/http.js +99 -0
- package/dist/server/http.js.map +1 -0
- package/dist/tools/account.js +126 -0
- package/dist/tools/account.js.map +1 -0
- package/dist/tools/bonus.js +151 -0
- package/dist/tools/bonus.js.map +1 -0
- package/dist/tools/cache.js +44 -0
- package/dist/tools/cache.js.map +1 -0
- package/dist/tools/cart.js +127 -0
- package/dist/tools/cart.js.map +1 -0
- package/dist/tools/common.js +173 -0
- package/dist/tools/common.js.map +1 -0
- package/dist/tools/favorites.js +103 -0
- package/dist/tools/favorites.js.map +1 -0
- package/dist/tools/index.js +24 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/orders.js +141 -0
- package/dist/tools/orders.js.map +1 -0
- package/dist/tools/products.js +165 -0
- package/dist/tools/products.js.map +1 -0
- package/dist/tools/receipts.js +52 -0
- package/dist/tools/receipts.js.map +1 -0
- package/dist/tools/recipes.js +261 -0
- package/dist/tools/recipes.js.map +1 -0
- package/dist/tools/shoppingList.js +149 -0
- package/dist/tools/shoppingList.js.map +1 -0
- package/dist/tools/stores.js +108 -0
- package/dist/tools/stores.js.map +1 -0
- package/dist/tools/views.js +100 -0
- package/dist/tools/views.js.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
<p align="center"><img src="assets/logo.png" alt="" width="128" height="128"></p>
|
|
2
|
+
|
|
3
|
+
# albert-heijn-mcp
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/albert-heijn-mcp)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](.nvmrc)
|
|
8
|
+
[](https://modelcontextprotocol.io)
|
|
9
|
+
|
|
10
|
+
**Your Albert Heijn account, in your AI assistant.**
|
|
11
|
+
|
|
12
|
+
albert-heijn-mcp is a [Model Context Protocol](https://modelcontextprotocol.io) server for Albert Heijn 🇳🇱. Connect it to any MCP client and just ask: find products and bonus deals, plan meals from Allerhande recipes, keep your shopping list and delivery order up to date, and look back at what you've bought.
|
|
13
|
+
|
|
14
|
+
> [!NOTE]
|
|
15
|
+
> An unofficial project, not affiliated with or endorsed by Albert Heijn. It uses the same API as the AH mobile app, which may change without notice.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Contents
|
|
20
|
+
|
|
21
|
+
- [What you can ask](#what-you-can-ask)
|
|
22
|
+
- [Quick start](#quick-start)
|
|
23
|
+
- [Logging in](#logging-in)
|
|
24
|
+
- [Connecting a client](#connecting-a-client)
|
|
25
|
+
- [Configuration](#configuration)
|
|
26
|
+
- [Deploying to a server](#deploying-to-a-server)
|
|
27
|
+
- [Tools](#tools) and [limitations](#limitations)
|
|
28
|
+
- [Development](#development)
|
|
29
|
+
- [Troubleshooting](#troubleshooting)
|
|
30
|
+
|
|
31
|
+
## What you can ask
|
|
32
|
+
|
|
33
|
+
Ask in Dutch, English or any language your assistant speaks:
|
|
34
|
+
|
|
35
|
+
> *"Wat is er deze week in de bonus van wat ik meestal koop?"*
|
|
36
|
+
|
|
37
|
+
**Plan meals**
|
|
38
|
+
|
|
39
|
+
> *"Find a vegetarian Allerhande recipe under 30 minutes for two, and put the ingredients on my shopping list. I already have olive oil and salt."*
|
|
40
|
+
|
|
41
|
+
> *"Scale the panlasagne recipe to six people and tell me how much salmon I need."*
|
|
42
|
+
|
|
43
|
+
> *"Plan three weeknight dinners around what's on bonus this week."*
|
|
44
|
+
|
|
45
|
+
**Save money**
|
|
46
|
+
|
|
47
|
+
> *"Which products I usually buy are on bonus this week?"*
|
|
48
|
+
|
|
49
|
+
> *"Rebuild tonight's stir-fry with ingredients that are on bonus, without changing the recipe too much."*
|
|
50
|
+
|
|
51
|
+
> *"Is next week's bonus out yet? If not, when does it appear?"*
|
|
52
|
+
|
|
53
|
+
> *"What's in the 2+1 gratis kaas deal?"*
|
|
54
|
+
|
|
55
|
+
**Shop**
|
|
56
|
+
|
|
57
|
+
> *"Put the products I've had delivered at least three times back on my list."*
|
|
58
|
+
|
|
59
|
+
> *"Find organic, gluten-free pasta, cheapest first."*
|
|
60
|
+
|
|
61
|
+
> *"Compare the protein and sugar in these three yoghurts and add the best one to my list."*
|
|
62
|
+
|
|
63
|
+
> *"When can AH deliver on Saturday?"*
|
|
64
|
+
|
|
65
|
+
> *"The courgettes are sold out. What else would work in this recipe?"*
|
|
66
|
+
|
|
67
|
+
> *"Add two more packs of milk to my upcoming delivery."*
|
|
68
|
+
|
|
69
|
+
> *"Make a favourites list called Pasta night with everything from this recipe."*
|
|
70
|
+
|
|
71
|
+
> *"Any vandaag-af bread or vegetables at my local AH worth picking up tonight?"*
|
|
72
|
+
|
|
73
|
+
**Look back**
|
|
74
|
+
|
|
75
|
+
> *"How much did my in-store receipts add up to in September, and what were the five priciest items?"*
|
|
76
|
+
|
|
77
|
+
> *"Show the receipt from my last shop and list anything I bought more than once."*
|
|
78
|
+
|
|
79
|
+
## Quick start
|
|
80
|
+
|
|
81
|
+
**Requirements:** Node.js 24 (LTS) and an Albert Heijn account.
|
|
82
|
+
|
|
83
|
+
There's nothing to install: [connect a client](#connecting-a-client) with `npx -y albert-heijn-mcp`, which downloads and runs the [latest version](https://www.npmjs.com/package/albert-heijn-mcp), then ask it to log you in to Albert Heijn.
|
|
84
|
+
|
|
85
|
+
To install it permanently instead, run `npm install --global albert-heijn-mcp` and use the `albert-heijn-mcp` command. To [build from source](#development), clone the repository.
|
|
86
|
+
|
|
87
|
+
## Logging in
|
|
88
|
+
|
|
89
|
+
AH's login page has a captcha that only works on AH's own site, so logging in takes two steps:
|
|
90
|
+
|
|
91
|
+
1. **Ask your assistant to log you in.** It calls `ah_login` and gives you a link to AH's login page; locally, it also opens in your browser. Log in as usual.
|
|
92
|
+
2. **Paste the code back.** After you log in, AH redirects to a link meant for its iPhone app, which the browser can't open, so the page stays put. Open the developer console (Chrome: <kbd>⌘</kbd> <kbd>⌥</kbd> <kbd>J</kbd> on Mac, <kbd>Ctrl</kbd> <kbd>Shift</kbd> <kbd>J</kbd> on Windows/Linux) and find this line:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
Failed to launch 'appie://login-exit?code=…' because the scheme does not have a registered handler.
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Copy the `appie://login-exit?code=…` link into the chat. The code works once and expires quickly, so paste it right away.
|
|
99
|
+
|
|
100
|
+
You only log in once. Tokens are stored on your machine and refreshed automatically:
|
|
101
|
+
|
|
102
|
+
| OS | Location |
|
|
103
|
+
|---|---|
|
|
104
|
+
| macOS | `~/Library/Application Support/albert-heijn-mcp/tokens.json` |
|
|
105
|
+
| Linux | `~/.config/albert-heijn-mcp/tokens.json` |
|
|
106
|
+
| Windows | `%AppData%\albert-heijn-mcp\tokens.json` |
|
|
107
|
+
|
|
108
|
+
The file is readable only by your user. Override the location with `AH_TOKENS_PATH`.
|
|
109
|
+
|
|
110
|
+
## Connecting a client
|
|
111
|
+
|
|
112
|
+
albert-heijn-mcp works with any MCP client. It runs locally over stdio, or on a server over Streamable HTTP.
|
|
113
|
+
|
|
114
|
+
### Local clients (stdio)
|
|
115
|
+
|
|
116
|
+
Clients that start MCP servers as a local command run `npx -y albert-heijn-mcp`. Most of them take this JSON in their MCP settings:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"mcpServers": {
|
|
121
|
+
"ah": {
|
|
122
|
+
"command": "npx",
|
|
123
|
+
"args": ["-y", "albert-heijn-mcp"]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Where the settings live differs per client; see its documentation. Clients with a CLI usually have an add command instead, e.g. `<client> mcp add ah -- npx -y albert-heijn-mcp`. It is also listed in the [MCP Registry](https://registry.modelcontextprotocol.io), which some clients install from.
|
|
130
|
+
|
|
131
|
+
> [!TIP]
|
|
132
|
+
> Desktop apps don't load your shell profile, so they may not find `npx` (common with nvm). Then set `command` to the output of `which npx`. For a source checkout, use `node` with the argument `/path/to/albert-heijn-mcp/dist/index.js`.
|
|
133
|
+
|
|
134
|
+
### Remote clients (Streamable HTTP)
|
|
135
|
+
|
|
136
|
+
Web apps such as ChatGPT and Claude.ai only connect to servers on the internet. Set one up first ([Deploying to a server](#deploying-to-a-server)). The endpoint is `https://your-server/mcp`. Send the token as an `Authorization: Bearer YOUR_TOKEN` header if the client lets you set headers; otherwise put it in the URL: `https://your-server/mcp?token=YOUR_TOKEN`.
|
|
137
|
+
|
|
138
|
+
**ChatGPT**: needs Developer mode (Plus, Pro, Business, Enterprise and Education). Open Settings → advanced settings, turn on Developer mode, and create a connector with the URL. Set authentication to **No authentication**: ChatGPT offers only OAuth or none, so the token goes in the URL.
|
|
139
|
+
|
|
140
|
+
**Claude.ai**: Settings → Connectors → Add custom connector, then paste the URL with the token.
|
|
141
|
+
|
|
142
|
+
> [!IMPORTANT]
|
|
143
|
+
> A token in a URL can end up in proxy logs. Use a long random value (`openssl rand -hex 32`) and replace it if it leaks.
|
|
144
|
+
|
|
145
|
+
## Configuration
|
|
146
|
+
|
|
147
|
+
Settings are environment variables. They can also go in a `.env` file in the working directory (see [`.env.example`](.env.example)); variables already set in the environment take precedence.
|
|
148
|
+
|
|
149
|
+
| Variable | Default | Description |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| `AH_REMOTE` | `false` | Don't open a browser on login. Use on servers (same as `--remote`). |
|
|
152
|
+
| `AH_TOKENS_PATH` | [per OS](#logging-in) | Where to store login tokens. |
|
|
153
|
+
| `AH_MCP_HOST` | `127.0.0.1` | Interface the HTTP server listens on. Keep the default behind a reverse proxy. |
|
|
154
|
+
| `AH_MCP_PORT` | `3000` | HTTP server port. |
|
|
155
|
+
| `AH_MCP_BASE_URL` | `http://localhost:3000` | Public URL of the HTTP server. Set it on a server: for a non-local URL, the localhost-only `Host` check is turned off so a reverse proxy can forward requests. |
|
|
156
|
+
| `AH_MCP_TOKEN` | — | Secret required on every HTTP request, as `Authorization: Bearer …` or `?token=…`. Required for the HTTP transport, which doesn't start without it. |
|
|
157
|
+
| `AH_LOG_FILE` | — | Also append logs to this file. Logs always go to stderr. |
|
|
158
|
+
|
|
159
|
+
Command-line flags:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
node dist/index.js [--transport stdio|streamable-http] [--remote] [--version] [--help]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`stdio` (the default) is for local clients; `streamable-http` serves MCP at `/mcp`.
|
|
166
|
+
|
|
167
|
+
## Deploying to a server
|
|
168
|
+
|
|
169
|
+
albert-heijn-mcp runs as a hardened systemd service behind a reverse proxy, installed from the latest release.
|
|
170
|
+
|
|
171
|
+
1. **Prepare the server.** Install Node.js 24 and create a service user:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
sudo useradd -r -m -d /home/albert-heijn-mcp -s /sbin/nologin albert-heijn-mcp
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
2. **Configure it** in `/home/albert-heijn-mcp/.env`:
|
|
178
|
+
|
|
179
|
+
```env
|
|
180
|
+
AH_MCP_BASE_URL=https://albert-heijn-mcp.example.com
|
|
181
|
+
AH_MCP_TOKEN=<output of: openssl rand -hex 32>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
3. **Install it** with the [service unit](deploy/albert-heijn-mcp.service) that comes with the package (it runs in `--remote` mode):
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
sudo npm install --global --prefix /usr/local albert-heijn-mcp
|
|
188
|
+
sudo install -m 644 /usr/local/lib/node_modules/albert-heijn-mcp/deploy/albert-heijn-mcp.service /etc/systemd/system/
|
|
189
|
+
sudo systemctl daemon-reload
|
|
190
|
+
sudo systemctl enable --now albert-heijn-mcp
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
To update, run the same commands, then `sudo systemctl restart albert-heijn-mcp`.
|
|
194
|
+
|
|
195
|
+
4. **Add TLS** with a reverse proxy that forwards to `127.0.0.1:3000`. With Caddy:
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
albert-heijn-mcp.example.com {
|
|
199
|
+
reverse_proxy 127.0.0.1:3000
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The service can write only to `/home/albert-heijn-mcp`, where it keeps its tokens. If you point `AH_LOG_FILE` elsewhere, add that path to `ReadWritePaths` in the unit file.
|
|
204
|
+
|
|
205
|
+
## Tools
|
|
206
|
+
|
|
207
|
+
Read-only tools are marked as such, so clients can run them without asking. Tools that remove data are marked destructive, so clients ask for confirmation first.
|
|
208
|
+
|
|
209
|
+
Tools that return data also return it as [structured output](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content) with a declared schema, for clients that use it. Products and recipes in tool results include a `url` to their page on ah.nl, and the server asks the assistant to link their names to it.
|
|
210
|
+
|
|
211
|
+
<details open>
|
|
212
|
+
<summary><b>Account</b></summary>
|
|
213
|
+
|
|
214
|
+
| Tool | Description |
|
|
215
|
+
|---|---|
|
|
216
|
+
| `ah_login` | Log in: returns AH's login link, then completes the login with the code you paste back. |
|
|
217
|
+
| `ah_logout` | Delete the stored tokens, to switch accounts or reset a session. |
|
|
218
|
+
| `ah_get_member_profile` | Name, masked email, and bonus card number (last 4 digits). |
|
|
219
|
+
|
|
220
|
+
</details>
|
|
221
|
+
|
|
222
|
+
<details open>
|
|
223
|
+
<summary><b>Products & offers</b></summary>
|
|
224
|
+
|
|
225
|
+
| Tool | Description |
|
|
226
|
+
|---|---|
|
|
227
|
+
| `ah_search_products` | Search one or more keywords at once; Dutch terms work best. Filter by `bonus=true` or by `filters` (organic, vegan, gluten_free and other diets, allergens and labels), and `sort` by price, what you buy most, or Nutri-Score. |
|
|
228
|
+
| `ah_get_products` | Details for one or more products. `include_nutritional_info=true` adds the nutrition table. |
|
|
229
|
+
| `ah_get_product_alternatives` | Similar products and substitutes AH suggests for a product. |
|
|
230
|
+
| `ah_get_bonus_offers` | This week's bonus offers, or next week's with `period=next`. `previously_bought=true` limits them to products you bought before (AH's "Eerder gekocht"). Can filter by keyword. |
|
|
231
|
+
| `ah_get_bonus_group_products` | The individual products behind a group deal such as "2+1 gratis". |
|
|
232
|
+
| `ah_search_stores` | Nearby stores, by postal code or your own address. |
|
|
233
|
+
| `ah_get_last_chance_items` | Vandaag-af markdowns in a store; the one nearest your address by default. |
|
|
234
|
+
|
|
235
|
+
</details>
|
|
236
|
+
|
|
237
|
+
<details open>
|
|
238
|
+
<summary><b>Recipes</b></summary>
|
|
239
|
+
|
|
240
|
+
| Tool | Description |
|
|
241
|
+
|---|---|
|
|
242
|
+
| `ah_search_recipes` | Search Allerhande recipes; Dutch terms work best. |
|
|
243
|
+
| `ah_get_recipe` | Ingredients, steps, and nutrition per serving. `servings` scales the ingredients. |
|
|
244
|
+
| `ah_add_recipe_to_shopping_list` | Match a recipe's ingredients to products and add them to the list in one step. `skip` leaves out what you have; `dry_run=true` previews the matches. |
|
|
245
|
+
|
|
246
|
+
</details>
|
|
247
|
+
|
|
248
|
+
<details open>
|
|
249
|
+
<summary><b>Shopping list & favourites</b></summary>
|
|
250
|
+
|
|
251
|
+
| Tool | Description |
|
|
252
|
+
|---|---|
|
|
253
|
+
| `ah_get_shopping_list` | Your shopping list ("Mijn lijst"), the basket you fill while shopping. |
|
|
254
|
+
| `ah_add_to_shopping_list` | Put products on the list, with a quantity each. |
|
|
255
|
+
| `ah_add_free_text_to_shopping_list` | Add a free-text item, like "verse bloemen". |
|
|
256
|
+
| `ah_remove_from_shopping_list` | Remove products or free-text items. |
|
|
257
|
+
| `ah_clear_shopping_list` | Remove everything. Requires `confirm="yes"`. |
|
|
258
|
+
| `ah_get_favorite_lists` | Your favourite lists ("Mijn lijstjes"). |
|
|
259
|
+
| `ah_add_to_favorite_list` | Add products to a favourite list. |
|
|
260
|
+
| `ah_remove_from_favorite_list` | Take products off a favourite list. |
|
|
261
|
+
| `ah_create_favorite_list` | Start a new, empty favourite list. |
|
|
262
|
+
| `ah_delete_favorite_list` | Delete a favourite list and its items. Requires `confirm="yes"`. |
|
|
263
|
+
|
|
264
|
+
</details>
|
|
265
|
+
|
|
266
|
+
<details open>
|
|
267
|
+
<summary><b>Delivery order</b></summary>
|
|
268
|
+
|
|
269
|
+
Choosing a delivery or pick-up slot in the AH app moves your shopping list into an order. `ah_get_delivery_slots` shows when delivery is possible; the other tools work on that order.
|
|
270
|
+
|
|
271
|
+
| Tool | Description |
|
|
272
|
+
|---|---|
|
|
273
|
+
| `ah_get_delivery_slots` | Delivery windows at your address for the coming days. |
|
|
274
|
+
| `ah_get_cart` | Products in the active order, with total price and discount. |
|
|
275
|
+
| `ah_update_cart_item` | Change a product's quantity; 0 takes it out. |
|
|
276
|
+
| `ah_remove_from_cart` | Remove a product from the order. |
|
|
277
|
+
| `ah_clear_cart` | Remove everything from the order. Requires `confirm="yes"`. |
|
|
278
|
+
|
|
279
|
+
</details>
|
|
280
|
+
|
|
281
|
+
<details open>
|
|
282
|
+
<summary><b>Orders & receipts</b></summary>
|
|
283
|
+
|
|
284
|
+
| Tool | Description |
|
|
285
|
+
|---|---|
|
|
286
|
+
| `ah_get_orders` | Upcoming delivery orders, or past ones with `past=true`. |
|
|
287
|
+
| `ah_get_order_details` | Products in one order. |
|
|
288
|
+
| `ah_get_frequent_items` | Your most-ordered products, counted over your delivery orders. |
|
|
289
|
+
| `ah_get_receipts` | Recent in-store receipts (kassabonnen). |
|
|
290
|
+
| `ah_get_receipt_details` | Items, discounts and payment for one receipt. |
|
|
291
|
+
|
|
292
|
+
</details>
|
|
293
|
+
|
|
294
|
+
### Limitations
|
|
295
|
+
|
|
296
|
+
- **Delivery orders can't be started through the API.** `ah_get_delivery_slots` lists the windows, but booking one, which starts the order, happens in the AH app or on ah.nl. While the order is active, AH doesn't serve the shopping list; the tools say so and point to the order tools.
|
|
297
|
+
- **Ticking off shopping-list items isn't supported:** the API returns no usable item IDs.
|
|
298
|
+
- **Bonus Box**, AH's personal weekly deals, is not available: its API is unknown.
|
|
299
|
+
|
|
300
|
+
## Development
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
git clone https://github.com/olekpuchka/albert-heijn-mcp
|
|
304
|
+
cd albert-heijn-mcp
|
|
305
|
+
npm ci
|
|
306
|
+
npm run build # compile to dist/
|
|
307
|
+
npm run lint # type-check
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Run it from the checkout with `node dist/index.js`, or use `/path/to/albert-heijn-mcp/dist/index.js` as the argument in your client's config with `node` as the command.
|
|
311
|
+
|
|
312
|
+
| Path | Contents |
|
|
313
|
+
|---|---|
|
|
314
|
+
| [`src/index.ts`](src/index.ts), [`src/config.ts`](src/config.ts) | Entry point, flags and settings |
|
|
315
|
+
| [`src/ahapi/`](src/ahapi) | Client for AH's REST and GraphQL API, on Node's built-in `fetch` |
|
|
316
|
+
| [`src/auth/`](src/auth) | Login code exchange, token storage and refresh |
|
|
317
|
+
| [`src/server/`](src/server) | Streamable HTTP transport and token check |
|
|
318
|
+
| [`src/tools/`](src/tools) | The MCP tools, one file per area |
|
|
319
|
+
| [`deploy/`](deploy) | systemd unit, shipped in the package |
|
|
320
|
+
| [`listing/`](listing) | Name, descriptions and icon to use in connector settings and app directories ([how](listing/README.md)) |
|
|
321
|
+
| [`.github/`](.github) | CI, release workflow and Dependabot |
|
|
322
|
+
| [`assets/`](assets) | Logo for this README and the server icon shown by MCP clients |
|
|
323
|
+
|
|
324
|
+
The only runtime dependencies are the official [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) and Zod, which the SDK uses for tool schemas.
|
|
325
|
+
|
|
326
|
+
To call tools by hand, use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector):
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
npx @modelcontextprotocol/inspector node dist/index.js
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Before deploying a change, run a quick check against a real account: log in, search for `melk`, add a product to your shopping list and remove it again, then view your cart and orders.
|
|
333
|
+
|
|
334
|
+
To release, set the new version in `package.json` and in both places in [`server.json`](server.json), merge it to `main`, and push a tag: `git tag v1.2.3 && git push origin v1.2.3`. The [release workflow](.github/workflows/release.yml) checks that the versions match, builds the package, attaches it to the GitHub release as `albert-heijn-mcp.tgz`, publishes it to npm, and updates the [MCP Registry](https://registry.modelcontextprotocol.io) entry. npm accepts the workflow through [trusted publishing](https://docs.npmjs.com/trusted-publishers), so no npm token is stored.
|
|
335
|
+
|
|
336
|
+
## Troubleshooting
|
|
337
|
+
|
|
338
|
+
<details>
|
|
339
|
+
<summary><b>Login fails with "exchange code"</b></summary>
|
|
340
|
+
|
|
341
|
+
Codes work once and expire quickly. Ask to log in again and paste the new link straight away.
|
|
342
|
+
</details>
|
|
343
|
+
|
|
344
|
+
<details>
|
|
345
|
+
<summary><b>No "Failed to launch" line after logging in</b></summary>
|
|
346
|
+
|
|
347
|
+
Open the developer console before you submit the login form, or look for the `appie://login-exit?code=…` request in the Network tab. Browsers other than Chrome may show the link in an error page or dialog instead.
|
|
348
|
+
</details>
|
|
349
|
+
|
|
350
|
+
<details>
|
|
351
|
+
<summary><b>"Not logged in", or the session seems broken</b></summary>
|
|
352
|
+
|
|
353
|
+
Log out and back in through the assistant, or delete `tokens.json` from the [token location](#logging-in) and log in again.
|
|
354
|
+
</details>
|
|
355
|
+
|
|
356
|
+
<details>
|
|
357
|
+
<summary><b>"There is no active delivery order to change"</b></summary>
|
|
358
|
+
|
|
359
|
+
AH accepts order changes only once an order exists. Choose a delivery slot in the AH app or on ah.nl first.
|
|
360
|
+
</details>
|
|
361
|
+
|
|
362
|
+
<details>
|
|
363
|
+
<summary><b>"The shopping list is not available while a delivery order is active"</b></summary>
|
|
364
|
+
|
|
365
|
+
Choosing a slot moved your list into the order. Use `ah_get_cart` and `ah_update_cart_item` until the order is delivered or cancelled.
|
|
366
|
+
</details>
|
|
367
|
+
|
|
368
|
+
<details>
|
|
369
|
+
<summary><b>Port 3000 is in use</b></summary>
|
|
370
|
+
|
|
371
|
+
Set `AH_MCP_PORT` to another port, in the environment or `.env`.
|
|
372
|
+
</details>
|
|
373
|
+
|
|
374
|
+
## License
|
|
375
|
+
|
|
376
|
+
[Apache 2.0](LICENSE)
|
package/assets/icon.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Albert Heijn MCP Server
|
|
3
|
+
After=network-online.target
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
User=albert-heijn-mcp
|
|
8
|
+
EnvironmentFile=/home/albert-heijn-mcp/.env
|
|
9
|
+
ExecStart=/usr/local/bin/albert-heijn-mcp --transport streamable-http --remote
|
|
10
|
+
WorkingDirectory=/home/albert-heijn-mcp
|
|
11
|
+
Restart=on-failure
|
|
12
|
+
RestartSec=5
|
|
13
|
+
|
|
14
|
+
# Hardening: the server needs only the network and write access to its home
|
|
15
|
+
# directory (tokens live in /home/albert-heijn-mcp/.config). If AH_LOG_FILE points
|
|
16
|
+
# elsewhere, add it to ReadWritePaths. MemoryDenyWriteExecute is left out:
|
|
17
|
+
# Node's JIT compiler needs it off.
|
|
18
|
+
NoNewPrivileges=yes
|
|
19
|
+
UMask=0077
|
|
20
|
+
ProtectSystem=strict
|
|
21
|
+
ReadWritePaths=/home/albert-heijn-mcp
|
|
22
|
+
PrivateTmp=yes
|
|
23
|
+
PrivateDevices=yes
|
|
24
|
+
ProtectKernelTunables=yes
|
|
25
|
+
ProtectKernelModules=yes
|
|
26
|
+
ProtectKernelLogs=yes
|
|
27
|
+
ProtectControlGroups=yes
|
|
28
|
+
ProtectClock=yes
|
|
29
|
+
ProtectHostname=yes
|
|
30
|
+
RestrictNamespaces=yes
|
|
31
|
+
RestrictRealtime=yes
|
|
32
|
+
RestrictSUIDSGID=yes
|
|
33
|
+
LockPersonality=yes
|
|
34
|
+
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
|
35
|
+
CapabilityBoundingSet=
|
|
36
|
+
AmbientCapabilities=
|
|
37
|
+
SystemCallArchitectures=native
|
|
38
|
+
SystemCallFilter=@system-service
|
|
39
|
+
SystemCallErrorNumber=EPERM
|
|
40
|
+
|
|
41
|
+
[Install]
|
|
42
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CLIENT_NAME } from "./client.js";
|
|
2
|
+
/** AH login page. After login it redirects to appie://login-exit?code=... (see exchangeCode). */
|
|
3
|
+
export const LOGIN_URL = `https://login.ah.nl/login?client_id=${CLIENT_NAME}&response_type=code&redirect_uri=appie://login-exit`;
|
|
4
|
+
/** Exchanges an OAuth authorization code for tokens. */
|
|
5
|
+
export function exchangeCode(c, code) {
|
|
6
|
+
return c.request("/mobile-auth/v1/auth/token", {
|
|
7
|
+
method: "POST",
|
|
8
|
+
body: { clientId: CLIENT_NAME, code },
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
/** Exchanges a refresh token for a new pair. The old refresh token stops working (AH rotates them). */
|
|
12
|
+
export function refreshToken(c, refreshToken) {
|
|
13
|
+
return c.request("/mobile-auth/v1/auth/token/refresh", {
|
|
14
|
+
method: "POST",
|
|
15
|
+
body: { clientId: CLIENT_NAME, refreshToken },
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/ahapi/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,WAAW,EAAE,MAAM,aAAa,CAAC;AAEzD,iGAAiG;AACjG,MAAM,CAAC,MAAM,SAAS,GAAG,uCAAuC,WAAW,qDAAqD,CAAC;AAUjI,wDAAwD;AACxD,MAAM,UAAU,YAAY,CAAC,CAAW,EAAE,IAAY;IACpD,OAAO,CAAC,CAAC,OAAO,CAAQ,4BAA4B,EAAE;QACpD,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE;KACtC,CAAC,CAAC;AACL,CAAC;AAED,uGAAuG;AACvG,MAAM,UAAU,YAAY,CAAC,CAAW,EAAE,YAAoB;IAC5D,OAAO,CAAC,CAAC,OAAO,CAAQ,oCAAoC,EAAE;QAC5D,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE;KAC9C,CAAC,CAAC;AACL,CAAC"}
|