@cyanheads/openfoodfacts-mcp-server 0.3.7 → 0.3.9

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 (41) hide show
  1. package/AGENTS.md +4 -4
  2. package/CLAUDE.md +4 -4
  3. package/Dockerfile +3 -3
  4. package/README.md +42 -20
  5. package/changelog/0.3.x/0.3.8.md +35 -0
  6. package/changelog/0.3.x/0.3.9.md +24 -0
  7. package/dist/mcp-server/tools/definitions/browse-taxonomy.tool.d.ts.map +1 -1
  8. package/dist/mcp-server/tools/definitions/browse-taxonomy.tool.js +38 -10
  9. package/dist/mcp-server/tools/definitions/browse-taxonomy.tool.js.map +1 -1
  10. package/dist/mcp-server/tools/definitions/compare-products.tool.d.ts +2 -1
  11. package/dist/mcp-server/tools/definitions/compare-products.tool.d.ts.map +1 -1
  12. package/dist/mcp-server/tools/definitions/compare-products.tool.js +65 -29
  13. package/dist/mcp-server/tools/definitions/compare-products.tool.js.map +1 -1
  14. package/dist/mcp-server/tools/definitions/get-product.tool.d.ts +1 -1
  15. package/dist/mcp-server/tools/definitions/get-product.tool.d.ts.map +1 -1
  16. package/dist/mcp-server/tools/definitions/get-product.tool.js +101 -47
  17. package/dist/mcp-server/tools/definitions/get-product.tool.js.map +1 -1
  18. package/dist/mcp-server/tools/definitions/index.d.ts +19 -5
  19. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  20. package/dist/mcp-server/tools/definitions/search-products.tool.d.ts +16 -3
  21. package/dist/mcp-server/tools/definitions/search-products.tool.d.ts.map +1 -1
  22. package/dist/mcp-server/tools/definitions/search-products.tool.js +137 -51
  23. package/dist/mcp-server/tools/definitions/search-products.tool.js.map +1 -1
  24. package/dist/services/openfoodfacts/openfoodfacts-service.d.ts +20 -2
  25. package/dist/services/openfoodfacts/openfoodfacts-service.d.ts.map +1 -1
  26. package/dist/services/openfoodfacts/openfoodfacts-service.js +150 -42
  27. package/dist/services/openfoodfacts/openfoodfacts-service.js.map +1 -1
  28. package/dist/services/openfoodfacts/types.d.ts +49 -14
  29. package/dist/services/openfoodfacts/types.d.ts.map +1 -1
  30. package/dist/services/openfoodfacts/types.js +8 -4
  31. package/dist/services/openfoodfacts/types.js.map +1 -1
  32. package/dist/services/taxonomy/taxonomy-service.d.ts +7 -5
  33. package/dist/services/taxonomy/taxonomy-service.d.ts.map +1 -1
  34. package/dist/services/taxonomy/taxonomy-service.js +27 -9
  35. package/dist/services/taxonomy/taxonomy-service.js.map +1 -1
  36. package/dist/utils/markdown.d.ts +0 -12
  37. package/dist/utils/markdown.d.ts.map +1 -1
  38. package/dist/utils/markdown.js +2 -23
  39. package/dist/utils/markdown.js.map +1 -1
  40. package/package.json +9 -9
  41. package/server.json +3 -3
package/AGENTS.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** openfoodfacts-mcp-server
4
- **Version:** 0.3.7
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.14`
4
+ **Version:** 0.3.9
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.16`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
- **MCP SDK:** `@modelcontextprotocol/server` ^2.2.0
8
- **Zod:** ^4.6.5
7
+ **MCP SDK:** `@modelcontextprotocol/server` ^2.3.1
8
+ **Zod:** ^4.6.7
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
11
11
 
package/CLAUDE.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** openfoodfacts-mcp-server
4
- **Version:** 0.3.7
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.14`
4
+ **Version:** 0.3.9
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.16`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
- **MCP SDK:** `@modelcontextprotocol/server` ^2.2.0
8
- **Zod:** ^4.6.5
7
+ **MCP SDK:** `@modelcontextprotocol/server` ^2.3.1
8
+ **Zod:** ^4.6.7
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
11
11
 
package/Dockerfile CHANGED
@@ -14,7 +14,7 @@
14
14
  # output. A stage that compiles a native addon needs the target-arch toolchain
15
15
  # and cannot cross-compile this way — drop the flag there.
16
16
  # ==============================================================================
17
- FROM --platform=$BUILDPLATFORM oven/bun:1.4.2 AS build
17
+ FROM --platform=$BUILDPLATFORM oven/bun:1.4.4 AS build
18
18
 
19
19
  WORKDIR /usr/src/app
20
20
 
@@ -46,7 +46,7 @@ RUN bun run build
46
46
  # A clean image rather than `FROM build`: the build stage's node_modules holds
47
47
  # devDependencies.
48
48
  # ==============================================================================
49
- FROM --platform=$BUILDPLATFORM oven/bun:1.4.2 AS deps
49
+ FROM --platform=$BUILDPLATFORM oven/bun:1.4.4 AS deps
50
50
 
51
51
  WORKDIR /usr/src/app
52
52
 
@@ -114,7 +114,7 @@ RUN rm -rf node_modules/@socketsecurity/bun-security-scanner
114
114
  # dependencies and build artifacts. Its only Bun invocations are HEALTHCHECK
115
115
  # and CMD, which run on the real target at container start.
116
116
  # ==============================================================================
117
- FROM oven/bun:1.4.2-slim AS production
117
+ FROM oven/bun:1.4.4-slim AS production
118
118
 
119
119
  WORKDIR /usr/src/app
120
120
 
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-0.3.7-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/openfoodfacts-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.2.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/openfoodfacts-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/openfoodfacts-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.2-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-0.3.9-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/openfoodfacts-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.3.1-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/openfoodfacts-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/openfoodfacts-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.4-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
@@ -29,15 +29,15 @@
29
29
 
30
30
  ## Overview
31
31
 
32
- Packaged-food data from Open Food Facts, a crowd-sourced database of 3M+ products. Look up items by barcode, search by text, tags, allergens, and nutrient thresholds, compare products side by side, and resolve everyday terms to the canonical tags the filters take. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
32
+ Packaged-food data from Open Food Facts, a crowd-sourced database of 3M+ products. Look up items by barcode, search by text, tags, allergens, and nutrient thresholds, compare products side by side, and resolve everyday terms to the canonical tags the filters take.
33
33
 
34
34
  ### Tools
35
35
 
36
36
  | Tool | Description |
37
37
  |:-----|:------------|
38
38
  | `off_get_product` | Fetch one product by barcode: ingredients, allergens and traces, additives, scores, nutrition, and tags |
39
- | `off_search_products` | Search by text, tag filters, allergen exclusions, and per-100 g nutrient thresholds; returns summary rows with barcodes |
40
- | `off_compare_products` | Compare 2–10 products by barcode on per-100 g nutrition, Nutri-Score, NOVA, and Green-Score |
39
+ | `off_search_products` | Search by text, tag filters, allergen exclusions, and nutrient thresholds (per 100 g, or per 100 ml for liquids); returns summary rows with barcodes |
40
+ | `off_compare_products` | Compare up to 10 products per call by barcode on nutrition (per 100 g, or per 100 ml for liquids), Nutri-Score, NOVA, and Green-Score |
41
41
  | `off_browse_taxonomy` | Resolve a human term to the canonical tag ID that `off_search_products` filters on |
42
42
 
43
43
  ## Capability reference
@@ -45,32 +45,32 @@ Packaged-food data from Open Food Facts, a crowd-sourced database of 3M+ product
45
45
  ### `off_get_product` <sub>tool</sub>
46
46
 
47
47
  - `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), with `requested_fields` echoing what was fetched
48
- - 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 100 g and per serving, category/label/packaging/origin/country tags, `image_url`, and `completeness` (0–1)
48
+ - 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)
49
49
  - `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`
50
50
 
51
51
  ---
52
52
 
53
53
  ### `off_search_products` <sub>tool</sub>
54
54
 
55
- - `query` (up to 24 words; every word except common stop words must match the name, generic name, categories, labels, or brand) plus tag filters `categories_tag`, `brands_tag`, `labels_tag` (up to 10, all must apply), `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 per-100 g `nutrient_filters`, all combined as AND; `page_size` 1–50 (default 20), optional `sort_by`
56
- - Summary rows carry `barcode`, `product_name`, `brands`, `nutriscore_grade`, `nova_group`, `ecoscore_grade`, and `categories_tags`, alongside `total`, `total_is_lower_bound`, `last_page`, and `omitted`; responses using an exclusion carry `exclusion_coverage`, since products with no allergen data entered pass it
57
- - `query` or `nutrient_filters` routes the search to a text index that lags the live database (flagged in `text_index_snapshot`), serves 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, and a page past either bound fails as `page_out_of_range`
55
+ - `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`
56
+ - `page_size` defaults to 20 and is served at up to 50 per call; a larger value (up to 10,000) 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`)
57
+ - `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 either limit fails as `page_out_of_range`
58
58
 
59
59
  ---
60
60
 
61
61
  ### `off_compare_products` <sub>tool</sub>
62
62
 
63
- - 2–10 `barcodes`, returned one row each in input order
64
- - Rows carry `found`, `nutriscore_grade`, `nova_group`, `ecoscore_grade`, per-100 g energy, fat, saturated fat, sugars, salt, protein, and fiber, and `completeness`; missing values stay absent, never imputed
63
+ - 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
64
+ - Rows carry `found`, `nutriscore_grade`, `nova_group`, `ecoscore_grade`, energy, fat, saturated fat, sugars, salt, protein, and fiber (per 100 g, or per 100 ml for liquids), and `completeness`; missing values stay absent, never imputed
65
65
  - `not_found` lists barcodes with no contributor record and `failed` lists fetches that failed, each with a `reason`; a failed barcode gets no row and never blocks the rest
66
66
 
67
67
  ---
68
68
 
69
69
  ### `off_browse_taxonomy` <sub>tool</sub>
70
70
 
71
- - `facet` is one of `categories`, `labels`, `allergens`, `additives`, `countries`, `nova_groups`, `nutrition_grades`; `search` matches a substring of the tag ID, name, or a synonym ("shellfish" → `en:crustaceans`); `limit` 1–100 (default 20), with no paging
71
+ - `facet` is one of `categories`, `labels`, `allergens`, `additives`, `countries`, `nova_groups`, `nutrition_grades`; `search` matches a substring of the tag ID, name, or a synonym ("shellfish" → `en:crustaceans`); `limit` (default 20) returns up to 100 per call (a value up to 200 still returns 100); a cut result says how many matched; no paging
72
72
  - Returns `tags[]` of `id` and `name`, where `id` goes to `off_search_products` unchanged; `nova_groups` and `nutrition_grades` come back complete with `total_in_facet`
73
- - The five open facets resolve against the live Open Food Facts taxonomy and fall back to a small offline sample, with a `notice`, when it is unreachable or the budget is spent; omitting `search` returns only that sample
73
+ - The five open facets resolve against the live Open Food Facts taxonomy and fall back to a small offline sample, with a `notice`, when it is unavailable; omitting `search` returns only that sample
74
74
 
75
75
  ## Features
76
76
 
@@ -79,35 +79,57 @@ Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): s
79
79
  Open Food Facts-specific:
80
80
 
81
81
  - No API key; the identifying `User-Agent` Open Food Facts' terms require is built in
82
- - Per-endpoint request budgets (product 15/min, search 10/min, taxonomy 10/min), counted per upstream attempt; transient failures (5xx other than 501, timeouts, 429 with `Retry-After`) retry up to 4 attempts, while a 4xx or 501 is sent once
83
- - Nutriments normalized from hyphenated keys (`energy-kcal_100g` → `energy_kcal_100g`), with named macros plus `additional_100g` / `additional_serving` maps that carry each nutrient's own unit
84
- - Tag filters take canonical IDs (`en:organic`, `en:no-gluten`); on text searches a case variant, synonym, or brand name is canonicalized before it is sent (`US` → `en:united-states`, `Nutella` → `nutella`)
85
- - Every field is contributor-entered, so a missing one means not yet recorded rather than absent from the product; Nutri-Score, NOVA, and Green-Score are Open Food Facts' own computed grades, returned as-is, and carry regional formula caveats
82
+ - Per-endpoint request budgets (product 15/min, search 10/min, taxonomy 10/min); transient failures retry up to 4 attempts
83
+ - Nutriments normalized from hyphenated keys (`energy-kcal_100g` → `energy_kcal_100g`), with `additional_100g` / `additional_serving` maps that label each nutrient with its unit
84
+ - 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`)
85
+ - Every field is contributor-entered, so a missing one means not yet recorded; Nutri-Score, NOVA, and Green-Score are Open Food Facts' own computed grades, returned as-is, and carry regional formula caveats
86
86
 
87
87
  Agent-friendly output:
88
88
 
89
89
  - Per-serving nutrition carries its denominator: `serving_size`, `serving_quantity`, `serving_quantity_unit`, and a note when none is recorded
90
90
  - Graceful partial failure: `off_compare_products` returns resolved rows alongside `not_found` and `failed`
91
- - Explicit count bounds: `total_is_lower_bound`, `last_page`, and `omitted` on searches, and an exhausted page is reported as such rather than as zero matches
92
- - Typed failures: each carries a declared `reason` (`upstream_error`, `upstream_timeout`, `upstream_rejected`, `rate_limited`, plus per-tool input reasons) and a recovery hint on both client surfaces
91
+ - Explicit count bounds: `total_is_lower_bound`, `last_page`, and `omitted` on searches
92
+ - Typed failures: each carries a declared `reason` (`upstream_error`, `upstream_timeout`, `upstream_rejected`, `rate_limited`, plus per-tool input reasons) and a recovery hint; an incomplete upstream response is a retryable `upstream_error`, never an empty result or a not-found
93
93
 
94
94
  ## Getting started
95
95
 
96
96
  ### Public Hosted Instance
97
97
 
98
- A public instance is available at `https://openfoodfacts.caseyjhand.com/mcp` — no installation required. Point any MCP client at it via Streamable HTTP:
98
+ A public instance is available at `https://openfoodfacts.caseyjhand.com/mcp` — no installation required. It serves Streamable HTTP; each client takes its own config shape.
99
+
100
+ For Claude Code:
101
+
102
+ ```sh
103
+ claude mcp add --transport http openfoodfacts-mcp-server https://openfoodfacts.caseyjhand.com/mcp
104
+ ```
105
+
106
+ For Cline (`cline_mcp_settings.json`):
99
107
 
100
108
  ```json
101
109
  {
102
110
  "mcpServers": {
103
111
  "openfoodfacts-mcp-server": {
104
- "type": "streamable-http",
112
+ "type": "streamableHttp",
105
113
  "url": "https://openfoodfacts.caseyjhand.com/mcp"
106
114
  }
107
115
  }
108
116
  }
109
117
  ```
110
118
 
119
+ For Cursor (`mcp.json`):
120
+
121
+ ```json
122
+ {
123
+ "mcpServers": {
124
+ "openfoodfacts-mcp-server": {
125
+ "url": "https://openfoodfacts.caseyjhand.com/mcp"
126
+ }
127
+ }
128
+ }
129
+ ```
130
+
131
+ Claude Desktop adds remote servers under Customize → Connectors.
132
+
111
133
  ### Self-Hosted / Local
112
134
 
113
135
  No API key is required. Add the following to your MCP client configuration file.
@@ -0,0 +1,35 @@
1
+ ---
2
+ summary: "Open Food Facts read fixes: text search treats punctuation inside a word as part of it, malformed upstream bodies fail as retryable upstream_error, liquids are labeled per 100 ml, and over-cap page_size, barcodes, and limit are served bounded instead of rejected."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.3.8 — 2026-10-13
8
+
9
+ ## Added
10
+
11
+ - **`indexed_nutriments` on `off_search_products` rows** (#58) — a search with `nutrient_filters` returns the text index's values for the constrained nutrients, keyed like `off_get_product` (`sugars_100g`). They can differ from the current record.
12
+ - **`page_size` and `page_size_capped` on `off_search_products`** (#57) — report the page size served and whether the request was cut to it.
13
+ - **`unprocessed` on `off_compare_products`** (#57) — barcodes past the 10th distinct one, listed without being fetched.
14
+
15
+ ## Changed
16
+
17
+ - **Over-cap reads are served bounded instead of rejected** (#57) — `off_search_products` accepts `page_size` up to 10000 and serves 50 (`page` counts pages of 50); `off_compare_products` accepts up to 100 `barcodes` and fetches 10; `off_browse_taxonomy` accepts `limit` up to 200 and returns 100. A cut taxonomy listing, at any `limit`, names how many tags matched and the call that reaches more.
18
+ - **`off_search_products` text queries treat `_`, in-word `'` / `’` / `.`, and decimal commas as part of a word** (#50) — `milk-chocolate`, `milk/chocolate`, and `lait 1,5%` now require each part.
19
+ - **`off_compare_products` compares a repeated barcode once** (#57) — two identical barcodes yield one row and take one of the 10 slots.
20
+ - **Nutrition is labeled per 100 g, or per 100 ml for liquids** (#51) — across tool descriptions, output descriptions, and the table headings; values are unchanged, and a record with only per-serving values gets no per-100 heading.
21
+ - **`off_get_product` labels open-map nutrients with the unit of the nutrient itself** (#52), not its `_unit` sibling.
22
+ - **`off_compare_products` shows the brand on every text row label** (#56) — the Scores and Nutrition rows and the low-completeness note each read `name (brand)`, `Barcode N (brand)`, or `Barcode N`, so a brand-only product keeps its brand and two same-name products stay distinguishable.
23
+ - **Final result pages no longer carry `truncated`, `shown`, or `cap`** (#55) — `truncated` now means more results lie beyond the page.
24
+
25
+ ## Fixed
26
+
27
+ - **A 2xx body missing its result fields fails as the retryable `upstream_error`** (#54) — instead of reading as a product `not_found`, a zero-match search, or a tag with no match. In `off_compare_products` it lists in `failed`; in `off_browse_taxonomy` it takes the noticed offline fallback.
28
+ - **A non-JSON 2xx body fails as `upstream_error`** (#61), not a reason-less `ValidationError`.
29
+ - **Taxonomy lookups end as cancelled when the caller aborts** (#60), instead of degrading to `lookup_failed` or the offline sample.
30
+
31
+ ## Dependencies
32
+
33
+ - `@cyanheads/mcp-ts-core` ^0.13.14 → ^0.13.15 (framework skills synced)
34
+ - dev: `@types/node` ^26.6.4 → ^26.6.5
35
+ - Bun 1.4.2 → 1.4.3 (`packageManager`, Docker base images, README badge)
@@ -0,0 +1,24 @@
1
+ ---
2
+ summary: "Product text and images render through the shared Markdown helpers."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.3.9 — 2026-10-25
8
+
9
+ ## Changed
10
+
11
+ - **Product text** uses the framework's Markdown escaper across lookup, search, comparison tables, and taxonomy output, preserving literal entities, tildes, pipes, and ordinary underscores.
12
+ - **Product images** render as Markdown images for absolute HTTP(S) URLs; unsupported or malformed destinations remain literal text.
13
+ - **Maintenance** syncs framework guidance and audit classification, trims development files from MCPB bundles, documents request-state sealing, and gives client-specific hosted connection examples.
14
+ - **Runtime tests** load the config dependency graph during collection and shorten CPU sampling batches while retaining five samples, the input-size span, and growth thresholds.
15
+
16
+ ## Dependencies
17
+
18
+ - **`@cyanheads/mcp-ts-core`** ^0.13.15 → ^0.13.16 (Markdown-safe error, recovery, and enrichment text).
19
+ - **`zod`** ^4.6.5 → ^4.6.7.
20
+ - **`@biomejs/biome`** ^2.5.15 → ^2.6.2.
21
+ - **`ignore`** ^7.0.12 → ^7.0.13.
22
+ - **`tsc-alias`** ^1.9.7 → ^1.9.8.
23
+ - **`vitest`** ^5.0.3 → ^5.0.5.
24
+ - **Bun** 1.4.3 → 1.4.4 (`packageManager`, Docker images, and README badge).
@@ -1 +1 @@
1
- {"version":3,"file":"browse-taxonomy.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/browse-taxonomy.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAIjD,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;EA2IhC,CAAC"}
1
+ {"version":3,"file":"browse-taxonomy.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/browse-taxonomy.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAWjD,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;EAiKhC,CAAC"}
@@ -3,8 +3,14 @@
3
3
  * @module mcp-server/tools/definitions/browse-taxonomy
4
4
  */
5
5
  import { tool, z } from '@cyanheads/mcp-ts-core';
6
+ import { MAX_AUTOCOMPLETE_SIZE } from '../../../services/openfoodfacts/openfoodfacts-service.js';
6
7
  import { getTaxonomyService } from '../../../services/taxonomy/taxonomy-service.js';
7
8
  import { mdInline, mdInlineCode } from '../../../utils/markdown.js';
9
+ /**
10
+ * Most tags one call returns. A larger `limit`, up to the suggestions one live lookup reads
11
+ * (`MAX_AUTOCOMPLETE_SIZE`), returns this many and discloses the cut when more matched.
12
+ */
13
+ const TAXONOMY_LIMIT_CAP = 100;
8
14
  export const offBrowseTaxonomyTool = tool('off_browse_taxonomy', {
9
15
  title: 'Browse Food Facts Taxonomy',
10
16
  description: 'Resolve a human term to the canonical Open Food Facts tag ID that off_search_products filters on. Covers categories, labels/certifications, allergens, additives, countries, NOVA groups, and Nutri-Score grades. Pass a search term to resolve against the Open Food Facts vocabulary, which holds tens of thousands of tags; omitting it returns only a small reference list for each facet except NOVA groups and Nutri-Score grades, which are complete. Most tag IDs use the "en:" prefix (e.g. "en:organic", "en:no-gluten", "en:crustaceans"); NOVA groups return bare digits "1"-"4" and Nutri-Score grades bare letters "a"-"e". Pass the id through to off_search_products exactly as returned. Category tags are frequently plural ("kombucha" resolves to "en:kombuchas"), so use the returned id rather than constructing one.',
@@ -33,9 +39,9 @@ export const offBrowseTaxonomyTool = tool('off_browse_taxonomy', {
33
39
  .number()
34
40
  .int()
35
41
  .min(1)
36
- .max(100)
42
+ .max(MAX_AUTOCOMPLETE_SIZE)
37
43
  .default(20)
38
- .describe('Maximum entries to return (1–100, default 20). There is no offset or page input: Open Food Facts offers no cursor for this lookup, so narrow the search term rather than paging. The tag spelling the term itself (e.g. "lentil" → en:lentils) is listed first among the live matches, so it is not the one a small limit cuts.'),
44
+ .describe(`Maximum entries to return (default 20). One call returns at most ${TAXONOMY_LIMIT_CAP}: a larger value, up to ${MAX_AUTOCOMPLETE_SIZE} (the most suggestions one lookup reads), returns the first ${TAXONOMY_LIMIT_CAP} and, when more matched, says how many did. There is no offset or page input: Open Food Facts offers no cursor for this lookup, so narrow the search term rather than paging. The tag spelling the term itself (e.g. "lentil" → en:lentils) is listed first among the live matches, so it is not the one a small limit cuts.`),
39
45
  }),
40
46
  output: z.object({
41
47
  facet: z.string().describe('The facet name that was queried (echoes the input).'),
@@ -58,15 +64,19 @@ export const offBrowseTaxonomyTool = tool('off_browse_taxonomy', {
58
64
  notice: z
59
65
  .string()
60
66
  .optional()
61
- .describe('Caveat about the answer — that the listing is a limited reference list rather than the full vocabulary, that Open Food Facts was unreachable, or that nothing matched and why.'),
67
+ .describe(`Caveat about the answer — that the listing is a limited reference list rather than the full vocabulary, that the live vocabulary could not be reached or gave no usable answer, 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).`),
62
68
  truncated: z.boolean().optional().describe('True when more tags exist beyond the limit.'),
63
69
  shown: z.number().optional().describe('Number of tags returned.'),
64
- cap: z.number().optional().describe('The limit that was applied.'),
70
+ cap: z
71
+ .number()
72
+ .optional()
73
+ .describe(`The limit that was applied — at most ${TAXONOMY_LIMIT_CAP}.`),
65
74
  },
66
75
  async handler(input, ctx) {
67
76
  const svc = getTaxonomyService();
77
+ const limit = Math.min(input.limit, TAXONOMY_LIMIT_CAP);
68
78
  // The service trims and treats a blank term as absent, so the raw input goes through as given.
69
- const result = await svc.search(input.facet, input.search, input.limit, ctx);
79
+ const result = await svc.search(input.facet, input.search, limit, ctx);
70
80
  ctx.log.info('Taxonomy browsed', {
71
81
  facet: input.facet,
72
82
  search: input.search,
@@ -79,14 +89,32 @@ export const offBrowseTaxonomyTool = tool('off_browse_taxonomy', {
79
89
  // pre-filter vocabulary total.
80
90
  //
81
91
  // `notice` is one slot, and ctx.enrich.truncated() writes it from `guidance`, so the service's
82
- // caveat is routed through that argument when both apply rather than being emitted separately
83
- // and silently overwritten. It is the more useful next move than the generic raise-the-limit
84
- // default: on an unfiltered browse the answer is to pass a search term, not a larger cap.
92
+ // caveat follows the cut guidance in that argument rather than being emitted separately and
93
+ // silently overwritten: on an unfiltered browse it says to pass a search term.
94
+ //
95
+ // The cut guidance names the match count and a call that can bring more forward. Below the
96
+ // cap that is a larger limit, or a narrower term when there is one. At the cap a larger limit
97
+ // returns these same tags, so only a narrower term reaches the rest; a limit above the cap is
98
+ // named too, since it is no longer what the cut was measured against. With every match
99
+ // returned, nothing was withheld and nothing is disclosed. The cap branch can say "the term":
100
+ // every embedded sample is smaller than the cap, so only a live search term reaches it.
85
101
  if (result.tags.length < result.matched_in_facet) {
102
+ const matched = result.matched_in_facet;
103
+ let cut;
104
+ if (limit === TAXONOMY_LIMIT_CAP) {
105
+ cut = `${limit < input.limit ? `limit ${input.limit} is above the ${TAXONOMY_LIMIT_CAP} tags one call returns; the` : 'The'} term matched ${matched} tags, and these are the first ${TAXONOMY_LIMIT_CAP}. Narrow the search term with a more specific word to bring the tag you need into the first ${TAXONOMY_LIMIT_CAP} — there is no offset to page past them.`;
106
+ }
107
+ else {
108
+ const hasTerm = Boolean(input.search?.trim());
109
+ const raise = matched <= TAXONOMY_LIMIT_CAP
110
+ ? `Raise limit to ${matched} to list them all`
111
+ : `Raise limit to ${TAXONOMY_LIMIT_CAP} for the first ${TAXONOMY_LIMIT_CAP}`;
112
+ cut = `${hasTerm ? 'The term matched' : 'The listing holds'} ${matched} tags, and these are the first ${limit}. ${raise}${hasTerm ? ', or narrow the search term with a more specific word' : ''}.`;
113
+ }
86
114
  ctx.enrich.truncated({
87
115
  shown: result.tags.length,
88
- cap: input.limit,
89
- ...(result.notice && { guidance: result.notice }),
116
+ cap: limit,
117
+ guidance: [cut, result.notice].filter(Boolean).join(' '),
90
118
  });
91
119
  }
92
120
  else if (result.notice) {
@@ -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,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,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,GAAG,EAAE;aACL,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,GAAG,CAAC;aACR,OAAO,CAAC,EAAE,CAAC;aACX,QAAQ,CACP,iUAAiU,CAClU;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,gLAAgL,CACjL;QACH,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;QACzF,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC;QACjE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC;KACnE;IAED,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG;QACtB,MAAM,GAAG,GAAG,kBAAkB,EAAE,CAAC;QACjC,+FAA+F;QAC/F,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,KAAc,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAEtF,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,EAAE;YAC/B,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM;YAC5B,OAAO,EAAE,MAAM,CAAC,gBAAgB;SACjC,CAAC,CAAC;QAEH,qFAAqF;QACrF,yFAAyF;QACzF,sFAAsF;QACtF,+BAA+B;QAC/B,EAAE;QACF,+FAA+F;QAC/F,8FAA8F;QAC9F,6FAA6F;QAC7F,0FAA0F;QAC1F,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC;YACjD,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC;gBACnB,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM;gBACzB,GAAG,EAAE,KAAK,CAAC,KAAK;gBAChB,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;aAClD,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YACzB,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC;QAED,OAAO;YACL,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,GAAG,CAAC,MAAM,CAAC,cAAc,KAAK,SAAS,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC;SACtF,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE;QACjB,MAAM,KAAK,GAAa;YACtB,MAAM,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,IAAI,CAAC,MAAM,WAAW,MAAM,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,MAAM,CAAC,cAAc,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK;SACzI,CAAC;QAEF,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,0FAA0F;YAC1F,sFAAsF;YACtF,KAAK,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;YAC/C,OAAO,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7D,CAAC;QAED,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAC9B,6FAA6F;YAC7F,wEAAwE;YACxE,KAAK,CAAC,IAAI,CAAC,KAAK,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClE,CAAC;QAED,KAAK,CAAC,IAAI,CACR,qKAAqK,CACtK,CAAC;QAEF,OAAO,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7D,CAAC;CACF,CAAC,CAAC"}
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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"}
@@ -31,10 +31,11 @@ export declare const offCompareProductsTool: import("@cyanheads/mcp-ts-core").To
31
31
  reason: z.ZodString;
32
32
  error: z.ZodString;
33
33
  }, z.core.$strip>>>;
34
+ unprocessed: z.ZodOptional<z.ZodArray<z.ZodString>>;
34
35
  }, z.core.$strip>, readonly [{
35
36
  readonly reason: "upstream_error";
36
37
  readonly code: JsonRpcErrorCode.ServiceUnavailable;
37
- readonly when: "Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, or is unreachable for a barcode — reported in that barcode's failed entry rather than failing the call";
38
+ readonly when: "Open Food Facts returns a 5xx other than 501 or 504, serves an HTML error page with a 2xx or 5xx status, answers 2xx with a malformed envelope, or is unreachable for a barcode — reported in that barcode's failed entry rather than failing the call";
38
39
  readonly retryable: true;
39
40
  readonly thrownBy: "service";
40
41
  readonly recovery: "Retry the barcodes listed in failed after a brief pause. Rows that already resolved are kept, so only the failures need repeating.";
@@ -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;AAoJ3E,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+RjC,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;AAiL3E,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+TjC,CAAC"}