@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.
- package/AGENTS.md +4 -4
- package/CLAUDE.md +4 -4
- package/Dockerfile +3 -3
- package/README.md +42 -20
- package/changelog/0.3.x/0.3.8.md +35 -0
- package/changelog/0.3.x/0.3.9.md +24 -0
- package/dist/mcp-server/tools/definitions/browse-taxonomy.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/browse-taxonomy.tool.js +38 -10
- package/dist/mcp-server/tools/definitions/browse-taxonomy.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/compare-products.tool.d.ts +2 -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 +65 -29
- package/dist/mcp-server/tools/definitions/compare-products.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-product.tool.d.ts +1 -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 +101 -47
- package/dist/mcp-server/tools/definitions/get-product.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/index.d.ts +19 -5
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-products.tool.d.ts +16 -3
- package/dist/mcp-server/tools/definitions/search-products.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-products.tool.js +137 -51
- package/dist/mcp-server/tools/definitions/search-products.tool.js.map +1 -1
- package/dist/services/openfoodfacts/openfoodfacts-service.d.ts +20 -2
- package/dist/services/openfoodfacts/openfoodfacts-service.d.ts.map +1 -1
- package/dist/services/openfoodfacts/openfoodfacts-service.js +150 -42
- package/dist/services/openfoodfacts/openfoodfacts-service.js.map +1 -1
- package/dist/services/openfoodfacts/types.d.ts +49 -14
- package/dist/services/openfoodfacts/types.d.ts.map +1 -1
- package/dist/services/openfoodfacts/types.js +8 -4
- package/dist/services/openfoodfacts/types.js.map +1 -1
- package/dist/services/taxonomy/taxonomy-service.d.ts +7 -5
- package/dist/services/taxonomy/taxonomy-service.d.ts.map +1 -1
- package/dist/services/taxonomy/taxonomy-service.js +27 -9
- package/dist/services/taxonomy/taxonomy-service.js.map +1 -1
- package/dist/utils/markdown.d.ts +0 -12
- package/dist/utils/markdown.d.ts.map +1 -1
- package/dist/utils/markdown.js +2 -23
- package/dist/utils/markdown.js.map +1 -1
- package/package.json +9 -9
- 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.
|
|
5
|
-
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.
|
|
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.
|
|
8
|
-
**Zod:** ^4.6.
|
|
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.
|
|
5
|
-
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.
|
|
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.
|
|
8
|
-
**Zod:** ^4.6.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
[](./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
|
|
|
@@ -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.
|
|
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
|
|
40
|
-
| `off_compare_products` | Compare
|
|
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
|
|
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
|
|
56
|
-
-
|
|
57
|
-
- `query` or `nutrient_filters`
|
|
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–
|
|
64
|
-
- Rows carry `found`, `nutriscore_grade`, `nova_group`, `ecoscore_grade`,
|
|
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`
|
|
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
|
|
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)
|
|
83
|
-
- Nutriments normalized from hyphenated keys (`energy-kcal_100g` → `energy_kcal_100g`), with
|
|
84
|
-
- Tag filters take canonical IDs (`en:organic`, `en:no-gluten`); on text searches a case variant, synonym, or brand name is canonicalized
|
|
85
|
-
- Every field is contributor-entered, so a missing one means not yet recorded
|
|
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
|
|
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
|
|
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.
|
|
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": "
|
|
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;
|
|
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(
|
|
42
|
+
.max(MAX_AUTOCOMPLETE_SIZE)
|
|
37
43
|
.default(20)
|
|
38
|
-
.describe(
|
|
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(
|
|
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
|
|
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,
|
|
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
|
|
83
|
-
//
|
|
84
|
-
//
|
|
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:
|
|
89
|
-
|
|
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,
|
|
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;
|
|
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"}
|