picnic-mcp 0.4.0 → 1.0.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 (51) hide show
  1. package/README.md +64 -21
  2. package/dist/cli.js +52 -3
  3. package/dist/cli.js.map +1 -1
  4. package/dist/errors.d.ts +14 -0
  5. package/dist/errors.js +28 -0
  6. package/dist/errors.js.map +1 -1
  7. package/dist/format/deliveries.d.ts +3 -2
  8. package/dist/format/deliveries.js +5 -4
  9. package/dist/format/deliveries.js.map +1 -1
  10. package/dist/format/discover.d.ts +15 -0
  11. package/dist/format/discover.js +14 -0
  12. package/dist/format/discover.js.map +1 -0
  13. package/dist/format/product.d.ts +1 -1
  14. package/dist/format/product.js +1 -1
  15. package/dist/format/product.js.map +1 -1
  16. package/dist/format/products.d.ts +1 -1
  17. package/dist/format/products.js +1 -1
  18. package/dist/format/products.js.map +1 -1
  19. package/dist/format/recipes.d.ts +2 -1
  20. package/dist/format/recipes.js +1 -0
  21. package/dist/format/recipes.js.map +1 -1
  22. package/dist/http.d.ts +34 -0
  23. package/dist/http.js +113 -0
  24. package/dist/http.js.map +1 -0
  25. package/dist/index.d.ts +1 -0
  26. package/dist/recipe-link.d.ts +7 -0
  27. package/dist/recipe-link.js +55 -0
  28. package/dist/recipe-link.js.map +1 -0
  29. package/dist/server.d.ts +1 -1
  30. package/dist/tools/cart.js +128 -24
  31. package/dist/tools/cart.js.map +1 -1
  32. package/dist/tools/deliveries.js +21 -10
  33. package/dist/tools/deliveries.js.map +1 -1
  34. package/dist/tools/discover.d.ts +3 -0
  35. package/dist/tools/discover.js +124 -0
  36. package/dist/tools/discover.js.map +1 -0
  37. package/dist/tools/groups.d.ts +2 -2
  38. package/dist/tools/groups.js +10 -5
  39. package/dist/tools/groups.js.map +1 -1
  40. package/dist/tools/recipes.js +57 -7
  41. package/dist/tools/recipes.js.map +1 -1
  42. package/dist/tools/result.d.ts +9 -3
  43. package/dist/tools/result.js +11 -4
  44. package/dist/tools/result.js.map +1 -1
  45. package/dist/tools/search-products.d.ts +1 -1
  46. package/dist/tools/search-products.js +82 -8
  47. package/dist/tools/search-products.js.map +1 -1
  48. package/dist/tools/slots.js +1 -1
  49. package/dist/tools/slots.js.map +1 -1
  50. package/dist/types.d.ts +1 -0
  51. package/package.json +8 -4
package/README.md CHANGED
@@ -1,30 +1,48 @@
1
1
  # picnic-mcp
2
2
 
3
- An [MCP](https://modelcontextprotocol.io) server for the online supermarket [Picnic](https://picnic.app). It lets a model search Picnic's products and change your shopping cart. It can't place an order.
3
+ An [MCP](https://modelcontextprotocol.io) server for the online supermarket [Picnic](https://picnic.app). It lets a model search Picnic's products, fill your shopping cart, pick a delivery slot, show promotions and the products you usually buy, and use recipes. It can't place an order.
4
4
 
5
- Unofficial: not made or supported by Picnic. Built on [picnic-api](https://github.com/MRVDH/picnic-api).
5
+ Unofficial: not made or supported by Picnic. Built on [picnic-api](https://github.com/MRVDH/picnic-api). Tested with Dutch accounts; German and French accounts should work, but haven't been tested.
6
+
7
+ ## Part of a set
8
+
9
+ picnic-api, picnic-web and picnic-mcp are developed and released together. When the Picnic app changes how it gets its data, picnic-api follows it and picnic-web and picnic-mcp are updated in the same change, so their latest versions always work together.
10
+
11
+ | Project | What it is |
12
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
13
+ | [picnic-api](https://github.com/MRVDH/picnic-api) [![npm](https://img.shields.io/npm/v/picnic-api.svg?style=flat-square)](https://www.npmjs.com/package/picnic-api) | Node.js library for Picnic's API: products, cart, delivery slots, deliveries and recipes. |
14
+ | [picnic-web](https://github.com/MRVDH/picnic-web) | Web interface for Picnic, to shop from your browser. Live at [picnic.maartenvandenhoven.com](http://picnic.maartenvandenhoven.com). |
15
+ | [picnic-mcp](https://github.com/MRVDH/picnic-mcp) [![npm](https://img.shields.io/npm/v/picnic-mcp.svg?style=flat-square)](https://www.npmjs.com/package/picnic-mcp) | MCP server that lets AI assistants search products, fill your cart and pick a delivery slot. |
6
16
 
7
17
  ## Tools
8
18
 
9
- The tools come in groups. `cart`, `slots` and `product` are on by default; turn groups on or off with `PICNIC_MCP_TOOLS` (see Setup).
10
-
11
- | Group | Tool | What it does |
12
- | ------------ | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
13
- | `cart` | `search_products` | Searches products. Returns a `bestMatch` when one product fits (usually one you bought before), or 2 or 3 `choices` to ask you about. |
14
- | | `add_to_cart` | Adds a product by its id. Returns the quantity in the cart and the cart total, and replacements when the product can't be delivered. |
15
- | | `remove_from_cart` | Removes a product by its id. |
16
- | | `get_cart` | Shows the cart: products, quantities, prices, total and delivery window, and replacements for products that can't be delivered. |
17
- | `slots` | `get_delivery_slots` | Lists the delivery slots, two days at a time or for one date. |
18
- | | `select_delivery_slot` | Chooses the delivery slot of the cart. |
19
- | `product` | `get_product` | Shows a product's ingredients, allergens, nutrition, storage info, promotion and price. |
20
- | `deliveries` | `get_deliveries` | Shows upcoming deliveries: window, number of products, total, and the expected arrival while the driver is on the way. Optionally the products. |
21
- | `recipes` | `search_recipes` | Searches Picnic's recipes. |
22
- | | `get_recipe` | Shows a recipe's ingredients, allergens and steps. |
23
- | | `add_recipe_to_cart` | Adds a recipe's ingredients to the cart for a number of portions. |
24
- | | `remove_recipe_from_cart` | Removes a recipe and its ingredients from the cart. |
19
+ The tools come in groups. `cart`, `slots`, `product` and `discover` are on by default; turn groups on or off with `PICNIC_MCP_TOOLS` (see Setup).
20
+
21
+ | Group | Tool | What it does |
22
+ | ------------ | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
23
+ | `cart` | `search_products` | Searches products. Returns a `bestMatch` when one product fits (usually one you bought before), or 2 or 3 `choices` to ask you about. |
24
+ | | `add_to_cart` | Adds one or more products by their ids. Returns the quantity of each in the cart, the products that failed, the cart total, and replacements when a product can't be delivered. |
25
+ | | `set_cart_quantity` | Sets how many of a product are in the cart; 0 removes it. |
26
+ | | `remove_from_cart` | Removes a product by its id. |
27
+ | | `get_cart` | Shows the cart: products, quantities, prices, total and delivery window, and replacements for products that can't be delivered. |
28
+ | `slots` | `get_delivery_slots` | Lists the delivery slots, two days at a time or for one date. |
29
+ | | `select_delivery_slot` | Chooses the delivery slot of the cart. |
30
+ | `product` | `get_product` | Shows a product's ingredients, allergens, nutrition, storage info, promotion and price. |
31
+ | `discover` | `get_promotions` | Lists this week's promotions per section (e.g. per category), all promotions of a section, or the products of one promotion. |
32
+ | | `get_favorites` | Lists the products you buy, per aisle, as the app's "Favorieten" tab does. For "add my usual groceries". |
33
+ | `deliveries` | `get_deliveries` | Shows upcoming deliveries (window, number of products, total, and the expected arrival while the driver is on the way) or the last past ones. Optionally the products, e.g. to order the same again. |
34
+ | `recipes` | `search_recipes` | Searches Picnic's recipes. |
35
+ | | `get_saved_recipes` | Lists the recipes you saved in the app. |
36
+ | | `get_recipe` | Shows a recipe's ingredients, allergens and steps. The recipe tools also take a recipe link shared from the Picnic app. |
37
+ | | `add_recipe_to_cart` | Adds a recipe's ingredients to the cart for a number of portions. |
38
+ | | `remove_recipe_from_cart` | Removes a recipe and its ingredients from the cart. |
39
+ | | `save_recipe` | Saves a recipe in the app. |
40
+ | | `unsave_recipe` | Removes a recipe from your saved recipes. |
25
41
 
26
42
  Output is compact JSON with only what a model needs. It leaves out your name, address, payment details, order numbers and other account details. Keep in mind that whatever the tools return is sent to the model your MCP client uses.
27
43
 
44
+ When a search has no clear match and your MCP client can show a form ([elicitation](https://modelcontextprotocol.io/specification/latest/client/elicitation)), you pick the product in the client yourself, instead of the model asking you in the chat.
45
+
28
46
  ## Setup
29
47
 
30
48
  Requires Node.js 20 or later.
@@ -39,7 +57,9 @@ It asks for your country, email and password, and the code Picnic texts you when
39
57
 
40
58
  ### 2. Add the server to your MCP client
41
59
 
42
- Most clients take a configuration like this:
60
+ **Claude Desktop and other MCPB clients:** download `picnic-mcp.mcpb` from the [latest release](https://github.com/MRVDH/picnic-mcp/releases/latest), open it, and fill in the auth key.
61
+
62
+ **Other clients** take a configuration like this:
43
63
 
44
64
  ```json
45
65
  {
@@ -56,10 +76,33 @@ Most clients take a configuration like this:
56
76
  }
57
77
  ```
58
78
 
59
- `PICNIC_COUNTRY_CODE` is `NL`, `DE` or `FR` (default `NL`). To choose the tool groups, add `PICNIC_MCP_TOOLS` with a comma-separated list, e.g. `"cart,slots,product,deliveries,recipes"` (default `cart,slots,product`). The server runs over stdio.
79
+ `PICNIC_COUNTRY_CODE` is `NL`, `DE` or `FR` (default `NL`). To choose the tool groups, add `PICNIC_MCP_TOOLS` with a comma-separated list, e.g. `"cart,slots,product,discover,deliveries,recipes"` (default `cart,slots,product,discover`). Fewer tools help smaller models pick the right one. The server runs over stdio; see [HTTP](#http) to reach it over the network.
80
+
81
+ The server is also listed in the [MCP Registry](https://registry.modelcontextprotocol.io) as `io.github.MRVDH/picnic-mcp`.
60
82
 
61
83
  When the key expires, the tools say so; run `npx picnic-mcp login` again.
62
84
 
85
+ ## HTTP
86
+
87
+ `picnic-mcp http` serves the same tools over HTTP at `/mcp`, for clients that connect to a URL. It needs a token that clients send as `Authorization: Bearer <token>`; generate one with:
88
+
89
+ ```bash
90
+ openssl rand -hex 32
91
+ ```
92
+
93
+ ```bash
94
+ PICNIC_AUTH_KEY=<auth key> PICNIC_MCP_HTTP_TOKEN=<token> npx picnic-mcp http
95
+ ```
96
+
97
+ | Variable | |
98
+ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
99
+ | `PICNIC_MCP_HTTP_TOKEN` | Required, at least 32 characters. Anyone with it can change your cart, so keep it secret. |
100
+ | `PICNIC_MCP_HTTP_HOST` | Address to listen on. Default `127.0.0.1`, so only this machine can connect. |
101
+ | `PICNIC_MCP_HTTP_PORT` | Default `3000`. |
102
+ | `PICNIC_MCP_HTTP_ALLOWED_HOSTS` | Host names allowed besides localhost, comma-separated, e.g. the name of a reverse proxy in front of it. Requests with another Host or Origin header are refused. |
103
+
104
+ The server doesn't do HTTPS. To use it from another machine, put it behind a reverse proxy with HTTPS, and add the proxy's host name to `PICNIC_MCP_HTTP_ALLOWED_HOSTS`.
105
+
63
106
  ## Use it in your own code
64
107
 
65
108
  `createServer` returns the MCP server without reading environment variables or files, so you can run it in your own process and connect it to any MCP transport:
@@ -75,4 +118,4 @@ Pass `tools` to choose the tool groups (e.g. `tools: ["cart", "recipes"]`), `cli
75
118
 
76
119
  ### Product images
77
120
 
78
- Results that list products (search, the cart, an added product, product details) carry the image URLs of those products in `_meta["picnic-mcp/images"]`, by product id. MCP clients don't pass `_meta` to the model, so the images cost no tokens; your own code can use them, e.g. to show product photos. The images don't need the auth key. The key is exported as `IMAGES_META_KEY`.
121
+ Results that list products (search, the cart, added products, promotions, favorites, product details) carry the image URLs of those products in `_meta["picnic-mcp/images"]`, by product id. MCP clients don't pass `_meta` to the model, so the images cost no tokens; your own code can use them, e.g. to show product photos. The images don't need the auth key. The key is exported as `IMAGES_META_KEY`.
package/dist/cli.js CHANGED
@@ -3,6 +3,7 @@ import { serveStdio } from "@modelcontextprotocol/server/stdio";
3
3
  import { createInterface } from "node:readline/promises";
4
4
  import { Writable } from "node:stream";
5
5
  import PicnicClient from "picnic-api";
6
+ import { MIN_TOKEN_LENGTH, startHttpServer } from "./http.js";
6
7
  import { createServer } from "./server.js";
7
8
  import { DEFAULT_TOOL_GROUPS, TOOL_GROUPS, parseToolGroups } from "./tools/groups.js";
8
9
  import { COUNTRY_CODES } from "./types.js";
@@ -13,15 +14,23 @@ const USAGE = `Usage:
13
14
  PICNIC_MCP_TOOLS tool groups, comma-separated, from
14
15
  ${TOOL_GROUPS.join(", ")}
15
16
  (default ${DEFAULT_TOOL_GROUPS.join(",")})
17
+ picnic-mcp http Serve it over HTTP at /mcp instead, with the same variables and:
18
+ PICNIC_MCP_HTTP_TOKEN bearer token clients send, at least
19
+ ${MIN_TOKEN_LENGTH} characters (required)
20
+ PICNIC_MCP_HTTP_HOST address to listen on (default 127.0.0.1)
21
+ PICNIC_MCP_HTTP_PORT port (default 3000)
22
+ PICNIC_MCP_HTTP_ALLOWED_HOSTS host names allowed besides localhost,
23
+ comma-separated, e.g. a reverse proxy's
16
24
  picnic-mcp login Sign in to Picnic and print an auth key for PICNIC_AUTH_KEY.`;
17
25
  function parseCountryCode(value) {
18
- const code = (value ?? "NL").trim().toUpperCase();
26
+ const code = (value?.trim() || "NL").toUpperCase();
19
27
  if (!COUNTRY_CODES.includes(code)) {
20
28
  throw new Error(`Unknown country code "${value}". Use one of ${COUNTRY_CODES.join(", ")}.`);
21
29
  }
22
30
  return code;
23
31
  }
24
- async function serve() {
32
+ /** Reads the Picnic settings from the environment, or exits with a message. */
33
+ function picnicSettings() {
25
34
  const authKey = process.env.PICNIC_AUTH_KEY?.trim();
26
35
  if (!authKey) {
27
36
  console.error("PICNIC_AUTH_KEY is not set. Get an auth key with `npx picnic-mcp login`.\n\n" + USAGE);
@@ -30,7 +39,45 @@ async function serve() {
30
39
  const countryCode = parseCountryCode(process.env.PICNIC_COUNTRY_CODE);
31
40
  const toolsEnv = process.env.PICNIC_MCP_TOOLS?.trim();
32
41
  const tools = toolsEnv ? parseToolGroups(toolsEnv) : DEFAULT_TOOL_GROUPS;
33
- serveStdio(() => createServer({ authKey, countryCode, tools }));
42
+ return { authKey, countryCode, tools };
43
+ }
44
+ async function serve() {
45
+ const settings = picnicSettings();
46
+ serveStdio(() => createServer(settings));
47
+ }
48
+ async function serveHttp() {
49
+ const { authKey, countryCode, tools } = picnicSettings();
50
+ const token = process.env.PICNIC_MCP_HTTP_TOKEN?.trim() ?? "";
51
+ if (token.length < MIN_TOKEN_LENGTH) {
52
+ console.error(`PICNIC_MCP_HTTP_TOKEN must be set to a random value of at least ${MIN_TOKEN_LENGTH} characters, e.g. from \`openssl rand -hex 32\`. Clients send it as "Authorization: Bearer <token>".`);
53
+ process.exit(1);
54
+ }
55
+ const host = process.env.PICNIC_MCP_HTTP_HOST?.trim() || "127.0.0.1";
56
+ const port = Number(process.env.PICNIC_MCP_HTTP_PORT?.trim() || 3000);
57
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
58
+ throw new Error(`PICNIC_MCP_HTTP_PORT must be a port number, not "${process.env.PICNIC_MCP_HTTP_PORT}".`);
59
+ }
60
+ const allowedHosts = (process.env.PICNIC_MCP_HTTP_ALLOWED_HOSTS ?? "")
61
+ .split(",")
62
+ .map((name) => name.trim())
63
+ .filter(Boolean);
64
+ // One Picnic client for all requests; each request gets its own MCP server.
65
+ const client = new PicnicClient({ authKey, countryCode });
66
+ const server = await startHttpServer({
67
+ token,
68
+ host,
69
+ port,
70
+ allowedHosts,
71
+ createServer: () => createServer({ client, countryCode, tools }),
72
+ onError: (error) => console.error("[picnic-mcp]", error),
73
+ });
74
+ console.error(`picnic-mcp listening on ${server.url}`);
75
+ if (!["127.0.0.1", "localhost", "::1"].includes(host)) {
76
+ console.error("This address can be reached from other machines, and the server doesn't use HTTPS. Put it behind an HTTPS reverse proxy.");
77
+ }
78
+ for (const signal of ["SIGINT", "SIGTERM"]) {
79
+ process.once(signal, () => void server.close().then(() => process.exit(0)));
80
+ }
34
81
  }
35
82
  /** Asks for the login details on the terminal and prints the auth key. Stores nothing. */
36
83
  async function login() {
@@ -78,6 +125,8 @@ async function main() {
78
125
  return serve();
79
126
  if (command === "login")
80
127
  return login();
128
+ if (command === "http")
129
+ return serveHttp();
81
130
  console.error(command === "--help" || command === "-h" ? USAGE : `Unknown command "${command}".\n\n${USAGE}`);
82
131
  process.exit(command === "--help" || command === "-h" ? 0 : 1);
83
132
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/errors.d.ts CHANGED
@@ -3,3 +3,17 @@
3
3
  * they can be long HTML pages or contain account data.
4
4
  */
5
5
  export declare function describeError(error: unknown): string;
6
+ /** Added to an error of a tool that changes the cart, when the change may have happened anyway. */
7
+ export declare const CART_MAY_HAVE_CHANGED = "The cart may have changed anyway: check get_cart before trying again.";
8
+ /**
9
+ * Whether an error is about the one product a request was for (it doesn't exist, Picnic refused
10
+ * it), so the next product can still be tried. Not for the auth key, rate limiting or an outage,
11
+ * which make every request fail.
12
+ */
13
+ export declare function isProductError(error: unknown): boolean;
14
+ /**
15
+ * Whether a failed cart change may still have gone through: after a timeout, a network error or a
16
+ * server error the request may have reached Picnic. Not when Picnic rejected it (a 4xx, the auth
17
+ * key, rate limiting) or it failed before anything was sent.
18
+ */
19
+ export declare function mayHaveChangedCart(error: unknown): boolean;
package/dist/errors.js CHANGED
@@ -25,4 +25,32 @@ export function describeError(error) {
25
25
  }
26
26
  return "Could not reach Picnic. Try again later.";
27
27
  }
28
+ /** Added to an error of a tool that changes the cart, when the change may have happened anyway. */
29
+ export const CART_MAY_HAVE_CHANGED = "The cart may have changed anyway: check get_cart before trying again.";
30
+ /**
31
+ * Whether an error is about the one product a request was for (it doesn't exist, Picnic refused
32
+ * it), so the next product can still be tried. Not for the auth key, rate limiting or an outage,
33
+ * which make every request fail.
34
+ */
35
+ export function isProductError(error) {
36
+ if (PicnicClient.isProductNotFoundError(error))
37
+ return true;
38
+ if (!PicnicClient.isPicnicApiError(error) || /<html[\s>]/i.test(error.body))
39
+ return false;
40
+ return error.status >= 400 && error.status < 500 && ![401, 403, 429].includes(error.status);
41
+ }
42
+ /**
43
+ * Whether a failed cart change may still have gone through: after a timeout, a network error or a
44
+ * server error the request may have reached Picnic. Not when Picnic rejected it (a 4xx, the auth
45
+ * key, rate limiting) or it failed before anything was sent.
46
+ */
47
+ export function mayHaveChangedCart(error) {
48
+ if (PicnicClient.isPicnicApiError(error))
49
+ return error.status >= 500 && !/<html[\s>]/i.test(error.body);
50
+ if (PicnicClient.isProductNotFoundError(error) ||
51
+ PicnicClient.isUnexpectedPageFormatError(error)) {
52
+ return false;
53
+ }
54
+ return true;
55
+ }
28
56
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,YAAY,CAAC;AAEtC;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,YAAY,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QACzC,sFAAsF;QACtF,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,OAAO,kEAAkE,CAAC;QAC5E,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACjD,OAAO,kHAAkH,CAAC;QAC5H,CAAC;QACD,OAAO,6BAA6B,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC;IAC7F,CAAC;IACD,IAAI,YAAY,CAAC,sBAAsB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,OAAO,WAAW,KAAK,CAAC,SAAS,oFAAoF,CAAC;IACxH,CAAC;IACD,IAAI,YAAY,CAAC,2BAA2B,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,OAAO,uHAAuH,CAAC;IACjI,CAAC;IACD,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QAC5D,OAAO,2CAA2C,CAAC;IACrD,CAAC;IACD,OAAO,0CAA0C,CAAC;AACpD,CAAC"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,YAAY,CAAC;AAEtC;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,YAAY,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QACzC,sFAAsF;QACtF,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,OAAO,kEAAkE,CAAC;QAC5E,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACjD,OAAO,kHAAkH,CAAC;QAC5H,CAAC;QACD,OAAO,6BAA6B,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC;IAC7F,CAAC;IACD,IAAI,YAAY,CAAC,sBAAsB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,OAAO,WAAW,KAAK,CAAC,SAAS,oFAAoF,CAAC;IACxH,CAAC;IACD,IAAI,YAAY,CAAC,2BAA2B,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,OAAO,uHAAuH,CAAC;IACjI,CAAC;IACD,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QAC5D,OAAO,2CAA2C,CAAC;IACrD,CAAC;IACD,OAAO,0CAA0C,CAAC;AACpD,CAAC;AAED,mGAAmG;AACnG,MAAM,CAAC,MAAM,qBAAqB,GAChC,uEAAuE,CAAC;AAE1E;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,YAAY,CAAC,sBAAsB,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5D,IAAI,CAAC,YAAY,CAAC,gBAAgB,CAAC,KAAK,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1F,OAAO,KAAK,CAAC,MAAM,IAAI,GAAG,IAAI,KAAK,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AAC9F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,IAAI,YAAY,CAAC,gBAAgB,CAAC,KAAK,CAAC;QACtC,OAAO,KAAK,CAAC,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAChE,IACE,YAAY,CAAC,sBAAsB,CAAC,KAAK,CAAC;QAC1C,YAAY,CAAC,2BAA2B,CAAC,KAAK,CAAC,EAC/C,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -1,13 +1,14 @@
1
1
  import type { CountryCode, Delivery, DeliveryPosition, DeliverySummary } from "../types.js";
2
2
  import { type CartLineOutput } from "./cart.js";
3
3
  /**
4
- * An upcoming delivery as a tool shows it to a model. Delivery and order ids, payment details, the
4
+ * A delivery as a tool shows it to a model. Delivery and order ids, payment details, the
5
5
  * driver, the address and the route stay out.
6
6
  */
7
7
  export type DeliveryOutput = {
8
8
  /** The delivery window, e.g. "Thu 23 Oct, 18:00-19:00". */
9
9
  window?: string;
10
- itemCount: number;
10
+ /** Left out when unknown: Picnic's summary of a past delivery has 0. */
11
+ itemCount?: number;
11
12
  total: string;
12
13
  /** Set when the driver is on the way. */
13
14
  onTheWay?: true;
@@ -3,15 +3,16 @@ import { formatPrice, formatTime, formatWindow } from "./prices.js";
3
3
  export function formatDelivery(summary, position, delivery, countryCode) {
4
4
  const window = formatWindow(summary.windowStart, summary.windowEnd);
5
5
  const arrival = position?.inProgress ? expectedArrival(position) : null;
6
+ const items = delivery?.orders.flatMap((order) => order.items);
7
+ // Counted from the products when they're loaded, since the summary of a past delivery has 0.
8
+ const itemCount = items ? items.reduce((sum, item) => sum + item.quantity, 0) : summary.itemCount;
6
9
  return {
7
10
  ...(window && { window }),
8
- itemCount: summary.itemCount,
11
+ ...(itemCount > 0 && { itemCount }),
9
12
  total: formatPrice(summary.totalPrice, countryCode),
10
13
  ...(position?.inProgress && { onTheWay: true }),
11
14
  ...(arrival && { expectedArrival: arrival }),
12
- ...(delivery && {
13
- products: delivery.orders.flatMap((order) => order.items.map((item) => formatCartLine(item, countryCode))),
14
- }),
15
+ ...(items && { products: items.map((item) => formatCartLine(item, countryCode)) }),
15
16
  };
16
17
  }
17
18
  function expectedArrival(position) {
@@ -1 +1 @@
1
- {"version":3,"file":"deliveries.js","sourceRoot":"","sources":["../../src/format/deliveries.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,cAAc,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAkBpE,MAAM,UAAU,cAAc,CAC5B,OAAwB,EACxB,QAAiC,EACjC,QAAyB,EACzB,WAAwB;IAExB,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxE,OAAO;QACL,GAAG,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,CAAC;QACzB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,UAAU,EAAE,WAAW,CAAC;QACnD,GAAG,CAAC,QAAQ,EAAE,UAAU,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC/C,GAAG,CAAC,OAAO,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC;QAC5C,GAAG,CAAC,QAAQ,IAAI;YACd,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAC1C,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAC7D;SACF,CAAC;KACH,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,QAA0B;IACjD,IAAI,QAAQ,CAAC,cAAc,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC;QACrD,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;IACvF,CAAC;IACD,OAAO,QAAQ,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AACzF,CAAC"}
1
+ {"version":3,"file":"deliveries.js","sourceRoot":"","sources":["../../src/format/deliveries.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,cAAc,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAmBpE,MAAM,UAAU,cAAc,CAC5B,OAAwB,EACxB,QAAiC,EACjC,QAAyB,EACzB,WAAwB;IAExB,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxE,MAAM,KAAK,GAAG,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC/D,6FAA6F;IAC7F,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;IAClG,OAAO;QACL,GAAG,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,CAAC;QACzB,GAAG,CAAC,SAAS,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC;QACnC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,UAAU,EAAE,WAAW,CAAC;QACnD,GAAG,CAAC,QAAQ,EAAE,UAAU,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC/C,GAAG,CAAC,OAAO,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC;QAC5C,GAAG,CAAC,KAAK,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC;KACnF,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,QAA0B;IACjD,IAAI,QAAQ,CAAC,cAAc,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC;QACrD,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;IACvF,CAAC;IACD,OAAO,QAAQ,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AACzF,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { CountryCode, ProductTile, PromotionGroup } from "../types.js";
2
+ import { type ProductOutput } from "./products.js";
3
+ /** A promotion on several products, as a tool shows it to a model. */
4
+ export type PromotionOutput = {
5
+ /** For get_promotions, to list its products. */
6
+ promotionId: string;
7
+ name: string;
8
+ /** E.g. "Bv. sojayoghurt". */
9
+ example?: string;
10
+ /** E.g. "2 voor €3.99". */
11
+ label?: string;
12
+ };
13
+ export declare function formatPromotion(group: PromotionGroup): PromotionOutput;
14
+ /** Products from a promotions or favorites page; none of them is marked as bought before. */
15
+ export declare function formatTiles(tiles: ProductTile[], countryCode: CountryCode): ProductOutput[];
@@ -0,0 +1,14 @@
1
+ import { formatProduct } from "./products.js";
2
+ export function formatPromotion(group) {
3
+ return {
4
+ promotionId: group.id,
5
+ name: group.name,
6
+ ...(group.example && { example: group.example }),
7
+ ...(group.label && { label: group.label }),
8
+ };
9
+ }
10
+ /** Products from a promotions or favorites page; none of them is marked as bought before. */
11
+ export function formatTiles(tiles, countryCode) {
12
+ return tiles.map((tile) => formatProduct({ tile, boughtBefore: false }, countryCode));
13
+ }
14
+ //# sourceMappingURL=discover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../../src/format/discover.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,aAAa,EAAE,MAAM,eAAe,CAAC;AAalE,MAAM,UAAU,eAAe,CAAC,KAAqB;IACnD,OAAO;QACL,WAAW,EAAE,KAAK,CAAC,EAAE;QACrB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;QAChD,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;KAC3C,CAAC;AACJ,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,WAAW,CAAC,KAAoB,EAAE,WAAwB;IACxE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC;AACxF,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import type { CountryCode, Product } from "../types.js";
2
2
  /** A product's details as a tool shows them to a model. Optional fields are left out when empty. */
3
3
  export type ProductDetailsOutput = {
4
- id: string;
4
+ productId: string;
5
5
  name: string;
6
6
  brand?: string;
7
7
  size?: string;
@@ -13,7 +13,7 @@ export function formatProductDetails(product, countryCode) {
13
13
  .filter((section) => section.content.trim() && lines(section.content.split("\n")) !== nutritionText)
14
14
  .map((section) => ({ title: section.title, content: cut(section.content) }));
15
15
  return {
16
- id: product.id,
16
+ productId: product.id,
17
17
  name: product.name,
18
18
  ...(product.brand && { brand: product.brand }),
19
19
  ...(product.unitQuantity && { size: product.unitQuantity }),
@@ -1 +1 @@
1
- {"version":3,"file":"product.js","sourceRoot":"","sources":["../../src/format/product.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"product.js","sourceRoot":"","sources":["../../src/format/product.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import type { CountryCode, ProductTile } from "../types.js";
2
2
  /** A product as a tool shows it to a model. Optional fields are left out when empty. */
3
3
  export type ProductOutput = {
4
- id: string;
4
+ productId: string;
5
5
  name: string;
6
6
  brand?: string;
7
7
  size?: string;
@@ -7,7 +7,7 @@ export function formatProduct(candidate, countryCode) {
7
7
  const { tile } = candidate;
8
8
  const labels = tile.labels.map((label) => label.text).filter((text) => !!text);
9
9
  return {
10
- id: tile.id,
10
+ productId: tile.id,
11
11
  name: tile.namePrefix ? `${tile.namePrefix} ${tile.name}` : tile.name,
12
12
  ...(tile.brand && { brand: tile.brand }),
13
13
  ...(tile.unitQuantity && { size: tile.unitQuantity }),
@@ -1 +1 @@
1
- {"version":3,"file":"products.js","sourceRoot":"","sources":["../../src/format/products.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAyB1C;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,SAAoB,EAAE,WAAwB;IAC1E,MAAM,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC;IAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC/F,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI;QACrE,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;QACxC,GAAG,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC;QACrD,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC;QAClD,GAAG,CAAC,IAAI,CAAC,aAAa,KAAK,IAAI;YAC7B,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,YAAY,IAAI;YACxC,QAAQ,EAAE,WAAW,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC;SACvD,CAAC;QACJ,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;QACpD,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;QACpC,GAAG,CAAC,SAAS,CAAC,YAAY,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;QACrD,GAAG,CAAC,IAAI,CAAC,aAAa,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;KACjD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"products.js","sourceRoot":"","sources":["../../src/format/products.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAyB1C;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,SAAoB,EAAE,WAAwB;IAC1E,MAAM,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC;IAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC/F,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,EAAE;QAClB,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI;QACrE,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;QACxC,GAAG,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC;QACrD,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC;QAClD,GAAG,CAAC,IAAI,CAAC,aAAa,KAAK,IAAI;YAC7B,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,YAAY,IAAI;YACxC,QAAQ,EAAE,WAAW,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC;SACvD,CAAC;QACJ,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;QACpD,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;QACpC,GAAG,CAAC,SAAS,CAAC,YAAY,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;QACrD,GAAG,CAAC,IAAI,CAAC,aAAa,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;KACjD,CAAC;AACJ,CAAC"}
@@ -36,5 +36,6 @@ export type RecipeOutput = {
36
36
  ingredients: RecipeIngredientOutput[];
37
37
  steps?: string[];
38
38
  };
39
- export declare function formatRecipeSummary(recipe: SearchRecipe): RecipeSummaryOutput;
39
+ /** A recipe from search results or the saved recipes; both have these fields. */
40
+ export declare function formatRecipeSummary(recipe: Pick<SearchRecipe, "id" | "name" | "subtitle" | "tags">): RecipeSummaryOutput;
40
41
  export declare function formatRecipe(recipe: Recipe, countryCode: CountryCode): RecipeOutput;
@@ -1,6 +1,7 @@
1
1
  import { formatPrice } from "./prices.js";
2
2
  /** Steps are cut at this many characters each, to keep the output short. */
3
3
  const MAX_STEP = 600;
4
+ /** A recipe from search results or the saved recipes; both have these fields. */
4
5
  export function formatRecipeSummary(recipe) {
5
6
  return {
6
7
  recipeId: recipe.id,
@@ -1 +1 @@
1
- {"version":3,"file":"recipes.js","sourceRoot":"","sources":["../../src/format/recipes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,4EAA4E;AAC5E,MAAM,QAAQ,GAAG,GAAG,CAAC;AAuCrB,MAAM,UAAU,mBAAmB,CAAC,MAAoB;IACtD,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,EAAE;QACnB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrD,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;KACrD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,WAAwB;IACnE,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,EAAE;QACnB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,GAAG,CAAC,MAAM,CAAC,kBAAkB,KAAK,IAAI,IAAI,EAAE,kBAAkB,EAAE,MAAM,CAAC,kBAAkB,EAAE,CAAC;QAC5F,GAAG,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC;QAC9D,SAAS,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,SAAS,CAAC,UAAU,EAAE;QAC3F,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;YACnD,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,CAAC;YACjD,GAAG,CAAC,UAAU,CAAC,aAAa,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC,aAAa,EAAE,CAAC;YACxE,QAAQ,EAAE,UAAU,CAAC,QAAQ;YAC7B,GAAG,CAAC,UAAU,CAAC,UAAU,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,UAAU,EAAE,CAAC;YAC/D,GAAG,CAAC,UAAU,CAAC,KAAK,KAAK,IAAI,IAAI,EAAE,KAAK,EAAE,WAAW,CAAC,UAAU,CAAC,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC;YACvF,KAAK,EAAE,UAAU,CAAC,OAAO;YACzB,GAAG,CAAC,UAAU,CAAC,IAAI,KAAK,UAAU,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YACzD,GAAG,CAAC,CAAC,UAAU,CAAC,WAAW,IAAI;gBAC7B,WAAW,EAAE,UAAU,CAAC,iBAAiB,IAAI,eAAe;aAC7D,CAAC;SACH,CAAC,CAAC;QACH,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI;YAC7B,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAC/B,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAC9D;SACF,CAAC;KACH,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"recipes.js","sourceRoot":"","sources":["../../src/format/recipes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,4EAA4E;AAC5E,MAAM,QAAQ,GAAG,GAAG,CAAC;AAuCrB,iFAAiF;AACjF,MAAM,UAAU,mBAAmB,CACjC,MAA+D;IAE/D,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,EAAE;QACnB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrD,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;KACrD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,WAAwB;IACnE,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,EAAE;QACnB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,GAAG,CAAC,MAAM,CAAC,kBAAkB,KAAK,IAAI,IAAI,EAAE,kBAAkB,EAAE,MAAM,CAAC,kBAAkB,EAAE,CAAC;QAC5F,GAAG,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC;QAC9D,SAAS,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,SAAS,CAAC,UAAU,EAAE;QAC3F,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;YACnD,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,CAAC;YACjD,GAAG,CAAC,UAAU,CAAC,aAAa,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC,aAAa,EAAE,CAAC;YACxE,QAAQ,EAAE,UAAU,CAAC,QAAQ;YAC7B,GAAG,CAAC,UAAU,CAAC,UAAU,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,UAAU,EAAE,CAAC;YAC/D,GAAG,CAAC,UAAU,CAAC,KAAK,KAAK,IAAI,IAAI,EAAE,KAAK,EAAE,WAAW,CAAC,UAAU,CAAC,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC;YACvF,KAAK,EAAE,UAAU,CAAC,OAAO;YACzB,GAAG,CAAC,UAAU,CAAC,IAAI,KAAK,UAAU,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YACzD,GAAG,CAAC,CAAC,UAAU,CAAC,WAAW,IAAI;gBAC7B,WAAW,EAAE,UAAU,CAAC,iBAAiB,IAAI,eAAe;aAC7D,CAAC;SACH,CAAC,CAAC;QACH,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI;YAC7B,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAC/B,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAC9D;SACF,CAAC;KACH,CAAC;AACJ,CAAC"}
package/dist/http.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { type McpServer } from "@modelcontextprotocol/server";
2
+ /** The only path the server answers on. */
3
+ export declare const MCP_PATH = "/mcp";
4
+ /** The shortest token the server accepts, so a weak token can't be guessed. */
5
+ export declare const MIN_TOKEN_LENGTH = 32;
6
+ export type HttpOptions = {
7
+ /** Clients send it as `Authorization: Bearer <token>`. At least 32 characters. */
8
+ token: string;
9
+ /** Host names allowed in the Host and Origin headers besides localhost, e.g. a reverse proxy's. */
10
+ allowedHosts?: string[];
11
+ /** Called once per request: the MCP server that handles it. */
12
+ createServer: () => McpServer;
13
+ onError?: (error: Error) => void;
14
+ };
15
+ /**
16
+ * The HTTP handler of the MCP endpoint, in fetch terms. Before a request reaches the MCP server it
17
+ * must be for `/mcp`, have an allowed Host and Origin (against DNS rebinding) and carry the token.
18
+ */
19
+ export declare function createHttpHandler(options: HttpOptions): {
20
+ fetch: (request: Request) => Promise<Response>;
21
+ close: () => Promise<void>;
22
+ };
23
+ export type HttpServerOptions = HttpOptions & {
24
+ host: string;
25
+ port: number;
26
+ };
27
+ /**
28
+ * Serves the MCP endpoint over HTTP with Node's own server. It doesn't do TLS: when it isn't on
29
+ * localhost, put it behind an HTTPS reverse proxy.
30
+ */
31
+ export declare function startHttpServer(options: HttpServerOptions): Promise<{
32
+ url: string;
33
+ close: () => Promise<void>;
34
+ }>;