@cyanheads/openfoodfacts-mcp-server 0.4.1 → 0.4.2
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/AGENTS.md +8 -3
- package/CLAUDE.md +8 -3
- package/Dockerfile +18 -0
- package/README.md +34 -6
- package/changelog/0.4.x/0.4.2.md +33 -0
- package/dist/config/server-config.d.ts +1 -0
- package/dist/config/server-config.d.ts.map +1 -1
- package/dist/config/server-config.js +11 -0
- package/dist/config/server-config.js.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/browse-taxonomy.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/browse-taxonomy.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/compare-products.tool.d.ts +62 -1
- package/dist/mcp-server/tools/definitions/compare-products.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/compare-products.tool.js +211 -23
- package/dist/mcp-server/tools/definitions/compare-products.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-product.tool.d.ts +18 -1
- package/dist/mcp-server/tools/definitions/get-product.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-product.tool.js +68 -30
- package/dist/mcp-server/tools/definitions/get-product.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/index.d.ts +69 -28
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-products.tool.d.ts +8 -2
- package/dist/mcp-server/tools/definitions/search-products.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-products.tool.js +285 -74
- package/dist/mcp-server/tools/definitions/search-products.tool.js.map +1 -1
- package/dist/mcp-server/tools/read-source.d.ts +31 -0
- package/dist/mcp-server/tools/read-source.d.ts.map +1 -0
- package/dist/mcp-server/tools/read-source.js +45 -0
- package/dist/mcp-server/tools/read-source.js.map +1 -0
- package/dist/services/mirror/mirror-feeds.d.ts +58 -0
- package/dist/services/mirror/mirror-feeds.d.ts.map +1 -0
- package/dist/services/mirror/mirror-feeds.js +95 -0
- package/dist/services/mirror/mirror-feeds.js.map +1 -0
- package/dist/services/mirror/mirror-ingester.d.ts +112 -0
- package/dist/services/mirror/mirror-ingester.d.ts.map +1 -0
- package/dist/services/mirror/mirror-ingester.js +564 -0
- package/dist/services/mirror/mirror-ingester.js.map +1 -0
- package/dist/services/mirror/mirror-job.d.ts +9 -0
- package/dist/services/mirror/mirror-job.d.ts.map +1 -0
- package/dist/services/mirror/mirror-job.js +48 -0
- package/dist/services/mirror/mirror-job.js.map +1 -0
- package/dist/services/mirror/mirror-lock.d.ts +45 -0
- package/dist/services/mirror/mirror-lock.d.ts.map +1 -0
- package/dist/services/mirror/mirror-lock.js +128 -0
- package/dist/services/mirror/mirror-lock.js.map +1 -0
- package/dist/services/mirror/mirror-queries.d.ts +76 -0
- package/dist/services/mirror/mirror-queries.d.ts.map +1 -0
- package/dist/services/mirror/mirror-queries.js +281 -0
- package/dist/services/mirror/mirror-queries.js.map +1 -0
- package/dist/services/mirror/mirror-scheduler.d.ts +41 -0
- package/dist/services/mirror/mirror-scheduler.d.ts.map +1 -0
- package/dist/services/mirror/mirror-scheduler.js +131 -0
- package/dist/services/mirror/mirror-scheduler.js.map +1 -0
- package/dist/services/mirror/mirror-store.d.ts +150 -0
- package/dist/services/mirror/mirror-store.d.ts.map +1 -0
- package/dist/services/mirror/mirror-store.js +281 -0
- package/dist/services/mirror/mirror-store.js.map +1 -0
- package/dist/services/mirror/mirror-sync.d.ts +62 -0
- package/dist/services/mirror/mirror-sync.d.ts.map +1 -0
- package/dist/services/mirror/mirror-sync.js +226 -0
- package/dist/services/mirror/mirror-sync.js.map +1 -0
- package/dist/services/mirror/record-builder.d.ts +78 -0
- package/dist/services/mirror/record-builder.d.ts.map +1 -0
- package/dist/services/mirror/record-builder.js +957 -0
- package/dist/services/mirror/record-builder.js.map +1 -0
- package/dist/services/openfoodfacts/nutrition-sets.d.ts +132 -0
- package/dist/services/openfoodfacts/nutrition-sets.d.ts.map +1 -0
- package/dist/services/openfoodfacts/nutrition-sets.js +507 -0
- package/dist/services/openfoodfacts/nutrition-sets.js.map +1 -0
- package/dist/services/openfoodfacts/openfoodfacts-service.d.ts +191 -13
- package/dist/services/openfoodfacts/openfoodfacts-service.d.ts.map +1 -1
- package/dist/services/openfoodfacts/openfoodfacts-service.js +389 -37
- package/dist/services/openfoodfacts/openfoodfacts-service.js.map +1 -1
- package/dist/services/openfoodfacts/user-agent.d.ts +11 -0
- package/dist/services/openfoodfacts/user-agent.d.ts.map +1 -0
- package/dist/services/openfoodfacts/user-agent.js +17 -0
- package/dist/services/openfoodfacts/user-agent.js.map +1 -0
- package/dist/services/taxonomy/taxonomy-service.d.ts +49 -14
- package/dist/services/taxonomy/taxonomy-service.d.ts.map +1 -1
- package/dist/services/taxonomy/taxonomy-service.js +114 -27
- package/dist/services/taxonomy/taxonomy-service.js.map +1 -1
- package/package.json +7 -1
- package/server.json +3 -3
- package/skills/openfoodfacts-guide/SKILL.md +4 -2
package/AGENTS.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** openfoodfacts-mcp-server
|
|
4
|
-
**Version:** 0.4.
|
|
4
|
+
**Version:** 0.4.2
|
|
5
5
|
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.18`
|
|
6
6
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/server` ^2.4.0
|
|
@@ -206,16 +206,20 @@ See framework CLAUDE.md and the `api-errors` skill for the full auto-classificat
|
|
|
206
206
|
src/
|
|
207
207
|
index.ts # createApp() entry point
|
|
208
208
|
config/
|
|
209
|
-
server-config.ts # Server-specific env vars (OFF_BASE_URL, rate limits)
|
|
209
|
+
server-config.ts # Server-specific env vars (OFF_BASE_URL, rate limits, OFF_MIRROR_PATH)
|
|
210
210
|
services/
|
|
211
|
+
mirror/ # Optional local export mirror (OFF_MIRROR_PATH): store, record builder, ingester, queries, refresh schedule
|
|
211
212
|
openfoodfacts/
|
|
212
|
-
openfoodfacts-service.ts # Open Food Facts API client (HTTP, rate limiting, retry)
|
|
213
|
+
openfoodfacts-service.ts # Open Food Facts API client (HTTP, rate limiting, retry); reads the mirror first when one is ready
|
|
214
|
+
user-agent.ts # Identifying User-Agent shared by the client and the mirror's downloads
|
|
215
|
+
nutrition-sets.ts # Stored as-sold nutrition sets: choosing one for a prepared-only read
|
|
213
216
|
types.ts # Domain types
|
|
214
217
|
taxonomy/
|
|
215
218
|
taxonomy-service.ts # Tag vocabulary — live resolution, offline sample, merge/fallback policy, tag-value canonicalization
|
|
216
219
|
utils/
|
|
217
220
|
markdown.ts # Contextual Markdown escaping for crowd-sourced values in content[]
|
|
218
221
|
mcp-server/
|
|
222
|
+
tools/read-source.ts # How tool text names an answer's source (off_api / off_mirror) and the mirror freshness note
|
|
219
223
|
tools/definitions/
|
|
220
224
|
get-product.tool.ts # off_get_product
|
|
221
225
|
search-products.tool.ts # off_search_products
|
|
@@ -316,6 +320,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
|
|
|
316
320
|
| `bun run format` | Auto-fix formatting (safe fixes only) |
|
|
317
321
|
| `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
|
|
318
322
|
| `bun run test` | Run tests (Vitest — use `bun run test`, not `bun test`) |
|
|
323
|
+
| `bun run mirror:init` / `mirror:refresh` / `mirror:status` / `mirror:verify` | Build, refresh, inspect, and integrity-check the local export mirror at `OFF_MIRROR_PATH` (checkout or Docker image) |
|
|
319
324
|
| `bun run start:stdio` | Production mode (stdio) |
|
|
320
325
|
| `bun run start:http` | Production mode (HTTP) |
|
|
321
326
|
| `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
|
package/CLAUDE.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** openfoodfacts-mcp-server
|
|
4
|
-
**Version:** 0.4.
|
|
4
|
+
**Version:** 0.4.2
|
|
5
5
|
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.18`
|
|
6
6
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/server` ^2.4.0
|
|
@@ -206,16 +206,20 @@ See framework CLAUDE.md and the `api-errors` skill for the full auto-classificat
|
|
|
206
206
|
src/
|
|
207
207
|
index.ts # createApp() entry point
|
|
208
208
|
config/
|
|
209
|
-
server-config.ts # Server-specific env vars (OFF_BASE_URL, rate limits)
|
|
209
|
+
server-config.ts # Server-specific env vars (OFF_BASE_URL, rate limits, OFF_MIRROR_PATH)
|
|
210
210
|
services/
|
|
211
|
+
mirror/ # Optional local export mirror (OFF_MIRROR_PATH): store, record builder, ingester, queries, refresh schedule
|
|
211
212
|
openfoodfacts/
|
|
212
|
-
openfoodfacts-service.ts # Open Food Facts API client (HTTP, rate limiting, retry)
|
|
213
|
+
openfoodfacts-service.ts # Open Food Facts API client (HTTP, rate limiting, retry); reads the mirror first when one is ready
|
|
214
|
+
user-agent.ts # Identifying User-Agent shared by the client and the mirror's downloads
|
|
215
|
+
nutrition-sets.ts # Stored as-sold nutrition sets: choosing one for a prepared-only read
|
|
213
216
|
types.ts # Domain types
|
|
214
217
|
taxonomy/
|
|
215
218
|
taxonomy-service.ts # Tag vocabulary — live resolution, offline sample, merge/fallback policy, tag-value canonicalization
|
|
216
219
|
utils/
|
|
217
220
|
markdown.ts # Contextual Markdown escaping for crowd-sourced values in content[]
|
|
218
221
|
mcp-server/
|
|
222
|
+
tools/read-source.ts # How tool text names an answer's source (off_api / off_mirror) and the mirror freshness note
|
|
219
223
|
tools/definitions/
|
|
220
224
|
get-product.tool.ts # off_get_product
|
|
221
225
|
search-products.tool.ts # off_search_products
|
|
@@ -316,6 +320,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
|
|
|
316
320
|
| `bun run format` | Auto-fix formatting (safe fixes only) |
|
|
317
321
|
| `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
|
|
318
322
|
| `bun run test` | Run tests (Vitest — use `bun run test`, not `bun test`) |
|
|
323
|
+
| `bun run mirror:init` / `mirror:refresh` / `mirror:status` / `mirror:verify` | Build, refresh, inspect, and integrity-check the local export mirror at `OFF_MIRROR_PATH` (checkout or Docker image) |
|
|
319
324
|
| `bun run start:stdio` | Production mode (stdio) |
|
|
320
325
|
| `bun run start:http` | Production mode (HTTP) |
|
|
321
326
|
| `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
|
package/Dockerfile
CHANGED
|
@@ -143,12 +143,30 @@ COPY --from=deps /usr/src/app/node_modules ./node_modules
|
|
|
143
143
|
COPY --from=build /usr/src/app/dist ./dist
|
|
144
144
|
COPY --from=build /usr/src/app/skills ./skills
|
|
145
145
|
|
|
146
|
+
# Mirror lifecycle scripts (`bun run mirror:init|refresh|status|verify` via
|
|
147
|
+
# `docker exec <container> …`). The shared context shim is imported by the
|
|
148
|
+
# three named scripts, so it travels with them. Bun honors tsconfig `paths` at
|
|
149
|
+
# runtime: a checkout's tsconfig.json maps `@/*` to `./src/*`, and this emitted
|
|
150
|
+
# one maps it to the compiled `./dist/*`, so the same package scripts run in both.
|
|
151
|
+
COPY --from=build /usr/src/app/scripts/mirror-init.ts \
|
|
152
|
+
/usr/src/app/scripts/mirror-refresh.ts \
|
|
153
|
+
/usr/src/app/scripts/mirror-status.ts \
|
|
154
|
+
/usr/src/app/scripts/_mirror-context.ts \
|
|
155
|
+
./scripts/
|
|
156
|
+
RUN echo '{"compilerOptions":{"baseUrl":".","paths":{"@/*":["./dist/*"]}}}' > tsconfig.json
|
|
157
|
+
|
|
146
158
|
# The 'oven/bun' image already provides a non-root user named 'bun'.
|
|
147
159
|
# We will use this existing user for enhanced security.
|
|
148
160
|
|
|
149
161
|
# Create and set permissions for the log directory, assigning ownership to the 'bun' user.
|
|
150
162
|
RUN mkdir -p /var/log/openfoodfacts-mcp-server && chown -R bun:bun /var/log/openfoodfacts-mcp-server
|
|
151
163
|
|
|
164
|
+
# Writable home for the export mirror (OFF_MIRROR_PATH, e.g. .mirror/openfoodfacts.sqlite),
|
|
165
|
+
# owned by the runtime user so a volume mounted over it is writable. A full init of the
|
|
166
|
+
# 4.9M-product export peaks at about 27 GB there while the export download sits beside the
|
|
167
|
+
# store, and leaves a store of about 14.3 GB.
|
|
168
|
+
RUN mkdir -p /usr/src/app/.mirror && chown -R bun:bun /usr/src/app/.mirror
|
|
169
|
+
|
|
152
170
|
# Switch to the non-root user
|
|
153
171
|
USER bun
|
|
154
172
|
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/openfoodfacts-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/openfoodfacts-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -53,7 +53,8 @@ Packaged-food data from Open Food Facts, a crowd-sourced database of 3M+ product
|
|
|
53
53
|
- `barcode` is digits only, 4–40 digits after any leading zeros; optional `fields` trims the response and pulls in what a field depends on (`nutriments` brings the serving size, quantity, and unit, and `serving_quantity` and `serving_quantity_unit` each bring the other plus the serving size), with `requested_fields` echoing what was fetched
|
|
54
54
|
- Returns ingredients (raw text plus a parsed tree up to three levels deep with `percent_estimate`), `allergens_tags`, `traces_tags`, `additives_tags`, `ingredients_analysis_tags` (vegan, vegetarian, palm-oil verdicts), `nutriscore_grade`, `nova_group`, `ecoscore_grade` (Green-Score), `nutriments` (per serving, and per 100 g, or per 100 ml for liquids), category/label/packaging/origin/country tags, `image_url`, and `completeness` (0–1)
|
|
55
55
|
- `traces_tags: ["en:none"]` means the label states no traces, while an empty array means none entered yet; a barcode no contributor has recorded fails as `not_found`
|
|
56
|
-
- Open Food Facts returns one preparation state of the nutrition per read. A read of prepared-product figures puts them under keys ending in `_prepared` in `additional_100g` and `additional_serving`, prints them in "as prepared" sections,
|
|
56
|
+
- Open Food Facts returns one preparation state of the nutrition per read. A read of prepared-product figures puts them under keys ending in `_prepared` in `additional_100g` and `additional_serving`, and prints them in "as prepared" sections. When a read returns only prepared figures, one more product read fetches the single as-sold set the record stores (`/api/v3.5` `fields=nutrition.input_sets`) and fills the named as-sold fields per 100 g, or per 100 ml for liquids, with no as-sold per-serving figure; `nutriments.stored_as_sold` reports `read`, `none` (the record stores no usable set), or `unread` with a `reason`, an `error`, and, when the failure named a wait, `retryAfter` in whole seconds (the call itself still succeeds). Other reads send nothing extra
|
|
57
|
+
- `source` (`off_api` or `off_mirror`) names where the product was read; a barcode a [local mirror](#local-export-mirror) holds is read from it with no upstream request, with `mirror_as_of` (the time of the newest Open Food Facts change the copy reflects) and a `mirror_snapshot` note, and the stored as-sold set comes from the same stored record
|
|
57
58
|
|
|
58
59
|
---
|
|
59
60
|
|
|
@@ -62,7 +63,8 @@ Packaged-food data from Open Food Facts, a crowd-sourced database of 3M+ product
|
|
|
62
63
|
- `query` (up to 24 words, all of which must match the name, generic name, categories, labels, or brand; punctuation separates words, so `milk-chocolate` requires both) plus the tag filters `categories_tag`, `brands_tag`, `labels_tag` (up to 10), `allergens_tag`, `traces_tag`, `ingredients_analysis_tag`, `additives_tag`, `nutrition_grade`, `nova_group`, `countries_tag`, the `exclude_allergens` / `exclude_traces` lists (up to 14 each), and up to 18 `nutrient_filters` (per 100 g, or per 100 ml for liquids), all combined as AND; optional `sort_by`
|
|
63
64
|
- `page_size` defaults to 20 and is served at up to 50 per call; a larger value, with no ceiling, is served at 50 and flagged in `page_size_capped`, and `page` counts pages of 50. Rows carry `barcode`, `product_name`, `brands`, `nutriscore_grade`, `nova_group`, `ecoscore_grade`, and `categories_tags`, plus `total`, `total_is_lower_bound`, `last_page`, `omitted`, and `truncated` (more matches beyond this page). Exclusions add `exclusion_coverage`, since products with no allergen data entered pass them; `nutrient_filters` add `indexed_nutriments` (the index values the filters checked, which can differ from `off_get_product`)
|
|
64
65
|
- The text index compares `energy-kcal`, `fat`, `saturated-fat`, `carbohydrates`, and `sugars` as whole numbers (`sugars lte 3` returns a row at 3.53). A `nutrient_filters` search sends `gt` and `gte` on those five as `[floor(value) TO *]` so no match is lost, then checks every returned row against every constraint at the index's 32-bit precision. Rows that fail leave the page and count in `outside_constraints`, so a page can come back short or empty while later pages hold matches. `total`, `total_is_lower_bound`, and `last_page` describe the index's whole-number answer and still count those rows. A range inside one whole number (`sugars gt 3.9` and `lt 4`) can pass few of the 10,000 reachable rows; add `query` or tag filters to narrow it
|
|
65
|
-
- `query` or `nutrient_filters` searches a text index that lags the live database (flagged in `text_index_snapshot`), reaches only the first 10,000 results, and rejects `additives_tag` as `additives_filter_needs_tag_search`; tag-only searches read the live database through page 10. A page past
|
|
66
|
+
- `query` or `nutrient_filters` searches a text index that lags the live database (flagged in `text_index_snapshot`), reaches only the first 10,000 results, and rejects `additives_tag` as `additives_filter_needs_tag_search`; tag-only searches read the live database through page 10, unless a [local mirror](#local-export-mirror) serves them: with every tag value confirmed, a mirror-served search has an exact `total` and reaches its first 10,000 results. A page past any of these limits fails as `page_out_of_range`
|
|
67
|
+
- `source` (`off_api` or `off_mirror`) names where the answer was read; from the mirror it also carries `mirror_as_of` and a `mirror_snapshot` note, and the default order and `sort_by` `popularity_key` and `unique_scans_n` follow the values stored with each record, which can differ from a live search, which ranks by its current scan counts
|
|
66
68
|
|
|
67
69
|
---
|
|
68
70
|
|
|
@@ -70,8 +72,9 @@ Packaged-food data from Open Food Facts, a crowd-sourced database of 3M+ product
|
|
|
70
72
|
|
|
71
73
|
- 2–100 `barcodes`, one row each in input order; repeated barcodes are compared once, and those past the 10th distinct one are listed in `unprocessed` without being fetched
|
|
72
74
|
- Rows carry `found`, `nutriscore_grade`, `nova_group`, `ecoscore_grade`, energy, fat, saturated fat, carbohydrates, sugars, salt, protein, and fiber (per 100 g, or per 100 ml for liquids), and `completeness`; missing values stay absent, never imputed
|
|
73
|
-
- A read of prepared-product figures adds a `prepared` object on the row with the same columns, read from the `*_prepared_100g` keys and never substituted for an as-sold figure. A row with as-sold figures appears in the as-sold table, a row with `prepared` in the "as prepared" table, and a row with both appears in both
|
|
74
|
-
-
|
|
75
|
+
- A read of prepared-product figures adds a `prepared` object on the row with the same columns, read from the `*_prepared_100g` keys and never substituted for an as-sold figure. A row whose read returned only prepared figures gets the same stored as-sold read as `off_get_product`, once every barcode's product read has settled, and reports it as `stored_as_sold` (`read`, `none`, or `unread`, which never moves the barcode into `failed`). A row with as-sold figures appears in the as-sold table, a row with `prepared` in the "as prepared" table, and a row with both appears in both
|
|
76
|
+
- Each row carries `source` (`off_api` or `off_mirror`), and `mirror_as_of` for a row read from a [local mirror](#local-export-mirror), so one comparison can mix the two; a `mirror_snapshot` note appears when any row came from it
|
|
77
|
+
- `not_found` lists barcodes with no contributor record and `failed` lists fetches that failed, each with a `reason` and, when the failure named a wait, `retryAfter` in whole seconds; a failed barcode gets no row and never blocks the rest
|
|
75
78
|
|
|
76
79
|
---
|
|
77
80
|
|
|
@@ -89,9 +92,11 @@ Open Food Facts-specific:
|
|
|
89
92
|
|
|
90
93
|
- No API key; the identifying `User-Agent` Open Food Facts' terms require is built in
|
|
91
94
|
- Per-endpoint request budgets (product 15/min, search 10/min, taxonomy 10/min); transient failures retry up to 4 attempts within a total deadline of 35 s for a product or search read and 20 s for a taxonomy lookup (one caller's search that canonicalizes a tag first can spend 55 s), after which the read fails as `upstream_timeout`, or `rate_limited` when its last finished attempt answered 429
|
|
95
|
+
- The stored as-sold read for a prepared-only product spends the product budget, so a barcode can take two product reads, and has its own 20 s deadline: a call settles within 35 s + 20 s = 55 s, and an unread set never fails the call or the row
|
|
96
|
+
- Optional [local export mirror](#local-export-mirror) (`OFF_MIRROR_PATH`): barcode reads and confirmed tag-only searches served from a SQLite copy of the export, kept current by daily deltas; a result names its `source` and, from the mirror, the time it reflects
|
|
92
97
|
- Nutriments normalized from hyphenated keys (`energy-kcal_100g` → `energy_kcal_100g`), with `additional_100g` / `additional_serving` maps that label each nutrient with its unit
|
|
93
98
|
- Tag filters take canonical IDs (`en:organic`, `en:no-gluten`); on text searches a case variant, synonym, or brand name is canonicalized first (`US` → `en:united-states`)
|
|
94
|
-
- Every field is contributor-entered, so a missing one means not yet recorded, except that a read
|
|
99
|
+
- Every field is contributor-entered, so a missing one means not yet recorded, except that a product read returning only prepared-product nutrition is followed by a read of the as-sold figures the record stores, reported as `stored_as_sold`; Nutri-Score, NOVA, and Green-Score are Open Food Facts' own computed grades, returned as-is, and carry regional formula caveats
|
|
95
100
|
|
|
96
101
|
Agent-friendly output:
|
|
97
102
|
|
|
@@ -137,6 +142,7 @@ No API key or account is needed: the server identifies itself to Open Food Facts
|
|
|
137
142
|
| `OFF_RATE_LIMIT_PRODUCT` | Product read budget (requests/min). The default is Open Food Facts' published per-IP limit; lower it on a shared outbound IP. | `15` |
|
|
138
143
|
| `OFF_RATE_LIMIT_SEARCH` | Search budget (requests/min). The default is Open Food Facts' published per-IP limit. | `10` |
|
|
139
144
|
| `OFF_RATE_LIMIT_TAXONOMY` | Taxonomy lookup budget (requests/min), shared by `off_browse_taxonomy`, tag canonicalization on text searches, and exclusion checks. | `10` |
|
|
145
|
+
| `OFF_MIRROR_PATH` | Path of the [local export mirror](#local-export-mirror)'s SQLite file. Unset disables the mirror. Works from a source checkout or the Docker image only. | unset |
|
|
140
146
|
| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
|
|
141
147
|
| `MCP_HTTP_PORT` | HTTP server port. | `3010` |
|
|
142
148
|
| `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth`. | `none` |
|
|
@@ -188,6 +194,26 @@ docker run --rm -p 3010:3010 ghcr.io/cyanheads/openfoodfacts-mcp-server:latest
|
|
|
188
194
|
|
|
189
195
|
The image defaults to HTTP transport, stateless session mode, and logs to `/var/log/openfoodfacts-mcp-server`. To run it over stdio from an MCP client, use `"command": "docker"` with `"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/openfoodfacts-mcp-server:latest"]`. To build locally, `docker build -t openfoodfacts-mcp-server .`; OpenTelemetry peer dependencies are installed by default — build with `--build-arg OTEL_ENABLED=false` to omit them.
|
|
190
196
|
|
|
197
|
+
#### Local export mirror
|
|
198
|
+
|
|
199
|
+
Setting `OFF_MIRROR_PATH` keeps a local SQLite copy of the Open Food Facts export. `off_get_product` and `off_compare_products` read it first by barcode and read live for a barcode it lacks. A search by tag filters alone is served from it when every value is a confirmed tag or brand slug, with an exact `total` through its first 10,000 results; every other search is live. A store error falls through to a live read. The mirror scripts need a checkout or the Docker image, so the npm install and `.mcpb` bundle stay live-only.
|
|
200
|
+
|
|
201
|
+
In the image, mount a volume at the store directory, `/usr/src/app/.mirror`, set `OFF_MIRROR_PATH` to a file in it, and build the store once with `docker exec`:
|
|
202
|
+
|
|
203
|
+
```sh
|
|
204
|
+
docker run -d --name openfoodfacts-mcp-server -p 3010:3010 \
|
|
205
|
+
-v off-mirror:/usr/src/app/.mirror \
|
|
206
|
+
-e OFF_MIRROR_PATH=/usr/src/app/.mirror/openfoodfacts.sqlite \
|
|
207
|
+
ghcr.io/cyanheads/openfoodfacts-mcp-server:latest
|
|
208
|
+
|
|
209
|
+
docker exec openfoodfacts-mcp-server bun run mirror:init # full load of the export, then the deltas since; re-run after an interrupt to resume
|
|
210
|
+
docker exec openfoodfacts-mcp-server bun run mirror:status # health, product count, and the as-of time answers cite
|
|
211
|
+
docker exec openfoodfacts-mcp-server bun run mirror:verify # status plus SQLite's integrity check
|
|
212
|
+
docker exec openfoodfacts-mcp-server bun run mirror:refresh # one delta pass by hand
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
A full `mirror:init` of the 4.9M-product export took about 1 h 48 min in testing, peaked at about 27 GB on the volume while the 13.4 GB download sat beside the store, left a 14.3 GB store, and used under 350 MB of memory. Reads stay live until `mirror:init` completes. Once `OFF_MIRROR_PATH` is set the server refreshes the mirror itself every three hours, at minute 30 of the server's local time: it applies each new daily delta, and reloads the whole export when the last full load is 30 days old and the export has changed since, because deltas never report deleted products. An export or delta line that is not valid JSON, or runs past 16 MiB, is skipped and logged by line number, and `mirror:status` prints how many the last full load skipped. `mirror:status` and `mirror:verify` exit non-zero unless a sync has completed and the store holds products. From a checkout, run the same `bun run mirror:*` scripts with `OFF_MIRROR_PATH` set in `.env`.
|
|
216
|
+
|
|
191
217
|
## Project structure
|
|
192
218
|
|
|
193
219
|
| Directory | Purpose |
|
|
@@ -196,6 +222,8 @@ The image defaults to HTTP transport, stateless session mode, and logs to `/var/
|
|
|
196
222
|
| `src/config` | Server-specific environment variable parsing and validation with Zod. |
|
|
197
223
|
| `src/mcp-server/tools` | Tool definitions (`*.tool.ts`). |
|
|
198
224
|
| `src/services/openfoodfacts` | Open Food Facts API client — HTTP, rate limiting, retry, field normalization. |
|
|
225
|
+
| `src/services/mirror` | Local export mirror — SQLite store, record builder, export and delta ingester, tag-search queries, and the refresh schedule. |
|
|
226
|
+
| `scripts/mirror-*.ts` | `mirror:init`, `mirror:refresh`, `mirror:status`, and `mirror:verify`. |
|
|
199
227
|
| `src/services/taxonomy` | Tag vocabulary — live resolution, offline sample, and tag-value canonicalization. |
|
|
200
228
|
| `src/utils` | Markdown escaping for contributor-entered values in text output. |
|
|
201
229
|
| `skills/` | Published Agent Skills for the agents that call the tools. |
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Adds an optional local mirror of the Open Food Facts export that serves barcode reads and tag-only searches, reads a record's stored as-sold figures when a product read returns only prepared ones, and names why a taxonomy lookup failed, with the wait in whole seconds."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.4.2 — 2026-11-01
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **Local export mirror** ([#79](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/79)): setting `OFF_MIRROR_PATH` keeps a SQLite copy of the Open Food Facts export. `off_get_product` and `off_compare_products` read it first by barcode and read live for a barcode it lacks. `bun run mirror:init`, `mirror:refresh`, `mirror:status`, and `mirror:verify` build and inspect the store from a checkout or the Docker image, and the server refreshes it every three hours. A refresh reloads the whole export every 30 days, or when the listed deltas no longer reach the store's checkpoint, unless one HEAD request finds the export's ETag unchanged since the last load. An export or delta line that is not valid JSON, or runs past 16 MiB, is skipped and logged by line number, and `mirror:status` prints how many the last full load skipped. Only `mirror:init` creates the store: the serving process reads live while the path names no file, and a refresh there is refused as `mirror_not_initialized`. Opening the store waits at most 250 ms on a sync's write lock before the call reads live, and a `mirror:*` script that meets that lock names the sync holding it. With the variable unset nothing opens or schedules.
|
|
12
|
+
- **Mirror-served tag-only searches** ([#79](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/79)): `off_search_products` with tag filters alone is served from the mirror when every tag value is confirmed, with an exact `total` through its first 10,000 results. Under the default order, `popularity_key`, and `unique_scans_n` the mirror sorts by the values stored with each record, so the order can differ from a live search.
|
|
13
|
+
- **`source`, `mirror_as_of`, `mirror_snapshot`** ([#79](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/79)): product, compare (per row), and search results name `off_api` or `off_mirror`; a mirror answer also carries when the copy was last updated and a note saying so.
|
|
14
|
+
- **`stored_as_sold`** ([#73](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/73)): when an `off_get_product` or `off_compare_products` read returns only prepared figures, one follow-up read fetches the record's stored as-sold set and fills the as-sold fields. The field reports `read`, `none`, or `unread` with a `reason`; an unread set never fails the call.
|
|
15
|
+
- **`cause` on `lookup_failed`** ([#77](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/77)): a failed taxonomy lookup reports `budget_spent`, `rate_limited`, or `unavailable`, and the exclusion refusal, the zero-result notice, and the `off_browse_taxonomy` notice name it, blaming the vocabulary only for `unavailable`.
|
|
16
|
+
- **`unconfirmed[]` on `page_out_of_range`** ([#79](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/79)): a tag-only page past 10 that the ready mirror cannot take because a value went unconfirmed is refused with those values named, and is retryable when each failed only at lookup.
|
|
17
|
+
|
|
18
|
+
## Changed
|
|
19
|
+
|
|
20
|
+
- **`retryAfter`** ([#76](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/76)): published in whole seconds on every failure that knows its wait. Each `failed[]` entry of `off_compare_products` carries it, and a retryable `unrecognized_exclusion` refusal carries the longest wait among its unchecked values.
|
|
21
|
+
- **`off_compare_products` stored sets** ([#73](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/73)): a row whose stored as-sold set was read stays in the as-sold table even when that set fills none of the compared columns.
|
|
22
|
+
- **Docs and skill**: tool descriptions, the server instructions, the `openfoodfacts-guide` skill, and the README describe the mirror, stored sets, and failure causes.
|
|
23
|
+
|
|
24
|
+
## Fixed
|
|
25
|
+
|
|
26
|
+
- **Shared taxonomy lookups** ([#75](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/75)): a caller that joined a lookup whose starter cancelled restarted it with a fresh 20 s deadline and could outlast 55 s. It now restarts with what is left of its own, so a single call settles within 55 s. A joined caller that is itself cancelled now ends at once instead of waiting for the shared answer.
|
|
27
|
+
- **Brand values with a language prefix** ([#79](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/79)): `xx:nutella` and `en:nutella` now drop the prefix before slugging. The text-search path had sent `xx-nutella` since 0.3.6.
|
|
28
|
+
|
|
29
|
+
## Dependencies
|
|
30
|
+
|
|
31
|
+
- **`node-cron`** ^4.6.0 (added, runs the scheduled refresh).
|
|
32
|
+
- **`better-sqlite3`** ^13.0.3 (added, dev; SQLite for the Node test lane).
|
|
33
|
+
- **`tsc-alias`** held at ^1.9.9 on the `devcheck.config.json` `outdated.allowlist`: installing 1.9.10 fails because the install's Socket security scanner gets a 403 from its upstream.
|
|
@@ -26,6 +26,7 @@ declare const ServerConfigSchema: z.ZodObject<{
|
|
|
26
26
|
rateLimitProduct: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
27
27
|
rateLimitSearch: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
28
28
|
rateLimitTaxonomy: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
29
|
+
mirrorPath: z.ZodOptional<z.ZodString>;
|
|
29
30
|
}, z.core.$strip>;
|
|
30
31
|
export type ServerConfig = z.infer<typeof ServerConfigSchema>;
|
|
31
32
|
/** Lazy-parsed server config from environment variables. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAG3C;;;;;;;;;;;;;;;;;GAiBG;AACH,QAAA,MAAM,kBAAkB
|
|
1
|
+
{"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAG3C;;;;;;;;;;;;;;;;;GAiBG;AACH,QAAA,MAAM,kBAAkB;;;;;;iBAiCtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D,4DAA4D;AAC5D,wBAAgB,eAAe,IAAI,YAAY,CAS9C"}
|
|
@@ -45,6 +45,16 @@ const ServerConfigSchema = z.object({
|
|
|
45
45
|
.min(1)
|
|
46
46
|
.default(10)
|
|
47
47
|
.describe('Taxonomy resolution rate limit (requests/min)'),
|
|
48
|
+
/**
|
|
49
|
+
* Unset means no mirror: no store opens and no refresh is scheduled. The `mirror:*` scripts build
|
|
50
|
+
* the store, and they run only from a checkout or the Docker image, so the variable appears in no
|
|
51
|
+
* npm-facing manifest.
|
|
52
|
+
*/
|
|
53
|
+
mirrorPath: z
|
|
54
|
+
.string()
|
|
55
|
+
.min(1)
|
|
56
|
+
.optional()
|
|
57
|
+
.describe('Local export mirror store path (SQLite file); unset disables the mirror'),
|
|
48
58
|
});
|
|
49
59
|
let _config;
|
|
50
60
|
/** Lazy-parsed server config from environment variables. */
|
|
@@ -54,6 +64,7 @@ export function getServerConfig() {
|
|
|
54
64
|
rateLimitProduct: 'OFF_RATE_LIMIT_PRODUCT',
|
|
55
65
|
rateLimitSearch: 'OFF_RATE_LIMIT_SEARCH',
|
|
56
66
|
rateLimitTaxonomy: 'OFF_RATE_LIMIT_TAXONOMY',
|
|
67
|
+
mirrorPath: 'OFF_MIRROR_PATH',
|
|
57
68
|
});
|
|
58
69
|
return _config;
|
|
59
70
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,OAAO,CAAC,iCAAiC,CAAC;SAC1C,QAAQ,CAAC,8BAA8B,CAAC;IAC3C,gBAAgB,EAAE,CAAC,CAAC,MAAM;SACvB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,wCAAwC,CAAC;IACrD,eAAe,EAAE,CAAC,CAAC,MAAM;SACtB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,kCAAkC,CAAC;IAC/C,iBAAiB,EAAE,CAAC,CAAC,MAAM;SACxB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,+CAA+C,CAAC;
|
|
1
|
+
{"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,OAAO,CAAC,iCAAiC,CAAC;SAC1C,QAAQ,CAAC,8BAA8B,CAAC;IAC3C,gBAAgB,EAAE,CAAC,CAAC,MAAM;SACvB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,wCAAwC,CAAC;IACrD,eAAe,EAAE,CAAC,CAAC,MAAM;SACtB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,kCAAkC,CAAC;IAC/C,iBAAiB,EAAE,CAAC,CAAC,MAAM;SACxB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,+CAA+C,CAAC;IAC5D;;;;OAIG;IACH,UAAU,EAAE,CAAC;SACV,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,EAAE;SACV,QAAQ,CAAC,yEAAyE,CAAC;CACvF,CAAC,CAAC;AAIH,IAAI,OAAiC,CAAC;AAEtC,4DAA4D;AAC5D,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,cAAc,CAAC,kBAAkB,EAAE;QAC7C,OAAO,EAAE,cAAc;QACvB,gBAAgB,EAAE,wBAAwB;QAC1C,eAAe,EAAE,uBAAuB;QACxC,iBAAiB,EAAE,yBAAyB;QAC5C,UAAU,EAAE,iBAAiB;KAC9B,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { createApp } from '@cyanheads/mcp-ts-core';
|
|
7
7
|
import { allToolDefinitions } from './mcp-server/tools/definitions/index.js';
|
|
8
|
+
import { startMirrorRefreshSchedule, stopMirrorRefreshSchedule, } from './services/mirror/mirror-scheduler.js';
|
|
9
|
+
import { closeMirrorStore } from './services/mirror/mirror-store.js';
|
|
8
10
|
import { initOpenFoodFactsService } from './services/openfoodfacts/openfoodfacts-service.js';
|
|
9
11
|
import { initTaxonomyService } from './services/taxonomy/taxonomy-service.js';
|
|
10
12
|
await createApp({
|
|
@@ -12,11 +14,16 @@ await createApp({
|
|
|
12
14
|
title: 'openfoodfacts-mcp-server',
|
|
13
15
|
tools: allToolDefinitions,
|
|
14
16
|
skills: 'skills',
|
|
15
|
-
instructions: 'Query Open Food Facts, a free crowd-sourced database of 3M+ packaged foods: off_get_product looks up one product by barcode (4–40 digits), off_search_products finds products by text or tag filters, off_compare_products puts several side by side on nutrition, and off_browse_taxonomy resolves a human term to the canonical tag ID the other tools filter on — so start there when you have a category or label rather than a barcode. Every field is contributor-entered, so a missing one means "not yet recorded" rather than absent, except that a read
|
|
17
|
+
instructions: 'Query Open Food Facts, a free crowd-sourced database of 3M+ packaged foods: off_get_product looks up one product by barcode (4–40 digits), off_search_products finds products by text or tag filters, off_compare_products puts several side by side on nutrition, and off_browse_taxonomy resolves a human term to the canonical tag ID the other tools filter on — so start there when you have a category or label rather than a barcode. Every field is contributor-entered, so a missing one means "not yet recorded" rather than absent, except that a product read returning only prepared-product nutrition is followed by a read of the as-sold figures the record stores, reported as stored_as_sold, and the computed Nutri-Score, NOVA, and Green-Score grades carry regional caveats. No API key is required; the data is ODbL 1.0, so cite Open Food Facts wherever you pass it on.',
|
|
16
18
|
sessionMode: 'stateless',
|
|
17
|
-
setup() {
|
|
19
|
+
async setup() {
|
|
18
20
|
initOpenFoodFactsService();
|
|
19
21
|
initTaxonomyService();
|
|
22
|
+
await startMirrorRefreshSchedule();
|
|
23
|
+
},
|
|
24
|
+
async teardown() {
|
|
25
|
+
await stopMirrorRefreshSchedule();
|
|
26
|
+
await closeMirrorStore();
|
|
20
27
|
},
|
|
21
28
|
});
|
|
22
29
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,wBAAwB,EAAE,MAAM,mDAAmD,CAAC;AAC7F,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAE9E,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,0BAA0B;IAChC,KAAK,EAAE,0BAA0B;IACjC,KAAK,EAAE,kBAAkB;IACzB,MAAM,EAAE,QAAQ;IAChB,YAAY,EACV,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,mDAAmD,CAAC;AAC7F,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAE9E,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,0BAA0B;IAChC,KAAK,EAAE,0BAA0B;IACjC,KAAK,EAAE,kBAAkB;IACzB,MAAM,EAAE,QAAQ;IAChB,YAAY,EACV,m2BAAm2B;IACr2B,WAAW,EAAE,WAAW;IACxB,KAAK,CAAC,KAAK;QACT,wBAAwB,EAAE,CAAC;QAC3B,mBAAmB,EAAE,CAAC;QACtB,MAAM,0BAA0B,EAAE,CAAC;IACrC,CAAC;IACD,KAAK,CAAC,QAAQ;QACZ,MAAM,yBAAyB,EAAE,CAAC;QAClC,MAAM,gBAAgB,EAAE,CAAC;IAC3B,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -69,7 +69,7 @@ export const offBrowseTaxonomyTool = tool('off_browse_taxonomy', {
|
|
|
69
69
|
notice: z
|
|
70
70
|
.string()
|
|
71
71
|
.optional()
|
|
72
|
-
.describe(`Caveat about the answer — that the listing is a limited reference list rather than the full vocabulary, that the live vocabulary
|
|
72
|
+
.describe(`Caveat about the answer — that the listing is a limited reference list rather than the full vocabulary, that the live vocabulary went unchecked and why (unreachable or no usable answer, Open Food Facts rate-limiting this server, or this server's own taxonomy budget spent, with the wait in seconds when one is known), that nothing matched and why, or that the limit cut matches, naming how many matched and the next call: a larger limit while one would list more, otherwise a narrower term, which is the only route past the first ${TAXONOMY_LIMIT_CAP} (a limit above ${TAXONOMY_LIMIT_CAP} is named too).`),
|
|
73
73
|
truncated: z.boolean().optional().describe('True when more tags exist beyond the limit.'),
|
|
74
74
|
shown: z.number().optional().describe('Number of tags returned.'),
|
|
75
75
|
cap: z
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browse-taxonomy.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/browse-taxonomy.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAc,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AACzF,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAE7D;;;GAGG;AACH,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC,qBAAqB,EAAE;IAC/D,KAAK,EAAE,4BAA4B;IACnC,WAAW,EACT,6yBAA6yB;IAC/yB,WAAW,EAAE;QACX,YAAY,EAAE,IAAI;QAClB,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;KACpB;IAED,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;QACd,KAAK,EAAE,CAAC;aACL,IAAI,CAAC;YACJ,YAAY;YACZ,QAAQ;YACR,WAAW;YACX,WAAW;YACX,WAAW;YACX,aAAa;YACb,kBAAkB;SACnB,CAAC;aACD,QAAQ,CACP,gbAAgb,CACjb;QACH,MAAM,EAAE,CAAC;aACN,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,4ZAA4Z,CAC7Z;QACH,8FAA8F;QAC9F,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,UAAU,CAAC,CAAC,EAAE,qDAAqD,CAAC;YACrE,+FAA+F;YAC/F,6DAA6D;aAC5D,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE;YACxB,KAAK,EAAE,qDAAqD;YAC5D,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;SAC1C,CAAC;aACD,GAAG,CAAC,CAAC,CAAC;aACN,OAAO,CAAC,EAAE,CAAC;aACX,QAAQ,CACP,oEAAoE,kBAAkB,sCAAsC,kBAAkB,8TAA8T,CAC7c;KACJ,CAAC;IAEF,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qDAAqD,CAAC;QACjF,IAAI,EAAE,CAAC;aACJ,KAAK,CACJ,CAAC;aACE,MAAM,CAAC;YACN,EAAE,EAAE,CAAC;iBACF,MAAM,EAAE;iBACR,QAAQ,CACP,kMAAkM,CACnM;YACH,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;SAC3E,CAAC;aACD,QAAQ,CAAC,qEAAqE,CAAC,CACnF;aACA,QAAQ,CAAC,uBAAuB,CAAC;QACpC,cAAc,EAAE,CAAC;aACd,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,8PAA8P,CAC/P;KACJ,CAAC;IAEF,UAAU,EAAE;QACV,MAAM,EAAE,CAAC;aACN,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,
|
|
1
|
+
{"version":3,"file":"browse-taxonomy.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/browse-taxonomy.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,48 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
6
|
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { type StoredAsSoldRead } from '../../../services/openfoodfacts/openfoodfacts-service.js';
|
|
8
|
+
import { type RawProduct } from '../../../services/openfoodfacts/types.js';
|
|
9
|
+
/** The failure reasons this tool declares — the four the service raises for a product fetch. */
|
|
10
|
+
declare const FAILURE_REASONS: readonly ['upstream_error', 'upstream_timeout', 'upstream_rejected', 'rate_limited'];
|
|
11
|
+
type FailureReason = (typeof FAILURE_REASONS)[number];
|
|
12
|
+
/**
|
|
13
|
+
* How the read of a record's stored as-sold set went, on a product or row whose read returned only
|
|
14
|
+
* prepared figures. `unread` is shaped like a `failed` entry, less the barcode its holder carries.
|
|
15
|
+
*/
|
|
16
|
+
export type StoredAsSold = {
|
|
17
|
+
status: 'read';
|
|
18
|
+
} | {
|
|
19
|
+
status: 'none';
|
|
20
|
+
} | {
|
|
21
|
+
status: 'unread';
|
|
22
|
+
reason: FailureReason;
|
|
23
|
+
error: string;
|
|
24
|
+
retryAfter?: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Settle a stored as-sold read onto the product read it follows: a chosen set joins the product's
|
|
28
|
+
* `nutriments`, and a failure leaves the product as it was beside an `unread` entry built the way
|
|
29
|
+
* `describeFailure` builds a `failed` one.
|
|
30
|
+
*/
|
|
31
|
+
export declare function settleStoredAsSold(barcode: string, product: RawProduct, read: StoredAsSoldRead): {
|
|
32
|
+
product: RawProduct;
|
|
33
|
+
stored_as_sold: StoredAsSold;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The `stored_as_sold` output field, one schema for both product tools. `subject` names what
|
|
37
|
+
* carries it ("this product", "this row").
|
|
38
|
+
*/
|
|
39
|
+
export declare function storedAsSoldSchema(subject: string): z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
40
|
+
status: z.ZodLiteral<"read">;
|
|
41
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
42
|
+
status: z.ZodLiteral<"none">;
|
|
43
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
44
|
+
status: z.ZodLiteral<"unread">;
|
|
45
|
+
reason: z.ZodString;
|
|
46
|
+
error: z.ZodString;
|
|
47
|
+
retryAfter: z.ZodOptional<z.ZodNumber>;
|
|
48
|
+
}, z.core.$strip>], "status">>;
|
|
7
49
|
export declare const offCompareProductsTool: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
8
50
|
barcodes: z.ZodArray<z.ZodString>;
|
|
9
51
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -25,7 +67,22 @@ export declare const offCompareProductsTool: import("@cyanheads/mcp-ts-core").To
|
|
|
25
67
|
saturated_fat_100g: z.ZodOptional<z.ZodNumber>;
|
|
26
68
|
sugars_100g: z.ZodOptional<z.ZodNumber>;
|
|
27
69
|
}, z.core.$strip>>;
|
|
70
|
+
stored_as_sold: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
71
|
+
status: z.ZodLiteral<"read">;
|
|
72
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
73
|
+
status: z.ZodLiteral<"none">;
|
|
74
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
75
|
+
status: z.ZodLiteral<"unread">;
|
|
76
|
+
reason: z.ZodString;
|
|
77
|
+
error: z.ZodString;
|
|
78
|
+
retryAfter: z.ZodOptional<z.ZodNumber>;
|
|
79
|
+
}, z.core.$strip>], "status">>;
|
|
28
80
|
completeness: z.ZodOptional<z.ZodNumber>;
|
|
81
|
+
source: z.ZodOptional<z.ZodEnum<{
|
|
82
|
+
off_api: "off_api";
|
|
83
|
+
off_mirror: "off_mirror";
|
|
84
|
+
}>>;
|
|
85
|
+
mirror_as_of: z.ZodOptional<z.ZodString>;
|
|
29
86
|
carbohydrates_100g: z.ZodOptional<z.ZodNumber>;
|
|
30
87
|
energy_kcal_100g: z.ZodOptional<z.ZodNumber>;
|
|
31
88
|
fat_100g: z.ZodOptional<z.ZodNumber>;
|
|
@@ -41,6 +98,7 @@ export declare const offCompareProductsTool: import("@cyanheads/mcp-ts-core").To
|
|
|
41
98
|
barcode: z.ZodString;
|
|
42
99
|
reason: z.ZodString;
|
|
43
100
|
error: z.ZodString;
|
|
101
|
+
retryAfter: z.ZodOptional<z.ZodNumber>;
|
|
44
102
|
}, z.core.$strip>>>;
|
|
45
103
|
unprocessed: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
46
104
|
}, z.core.$strip>, readonly [{
|
|
@@ -71,5 +129,8 @@ export declare const offCompareProductsTool: import("@cyanheads/mcp-ts-core").To
|
|
|
71
129
|
readonly retryable: true;
|
|
72
130
|
readonly thrownBy: "service";
|
|
73
131
|
readonly recovery: "Wait the seconds the failed entry names, or about a minute when it names none, then retry only those barcodes. Compare fewer barcodes per call to stay inside the budget.";
|
|
74
|
-
}],
|
|
132
|
+
}], {
|
|
133
|
+
readonly mirror_snapshot: z.ZodOptional<z.ZodString>;
|
|
134
|
+
}>;
|
|
135
|
+
export {};
|
|
75
136
|
//# sourceMappingURL=compare-products.tool.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compare-products.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/compare-products.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"compare-products.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/compare-products.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;AAW3E,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,mDAAmD,CAAC;AAC3D,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,mCAAmC,CAAC;AAiI3C,gGAAgG;AAChG,QAAA,MAAM,eAAe,YACnB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,CACN,CAAC;AAEX,KAAK,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAqCtD;;;GAGG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAkBpF;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,UAAU,EACnB,IAAI,EAAE,gBAAgB,GACrB;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,cAAc,EAAE,YAAY,CAAA;CAAE,CAgBvD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM;;;;;;;;;+BAkDjD;AA4HD,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsajC,CAAC"}
|