@cyanheads/openfoodfacts-mcp-server 0.1.7 → 0.1.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 +35 -28
- package/CLAUDE.md +35 -28
- package/Dockerfile +11 -7
- package/README.md +12 -8
- package/changelog/0.1.x/0.1.8.md +21 -0
- package/changelog/0.1.x/0.1.9.md +34 -0
- package/dist/mcp-server/tools/definitions/browse-taxonomy.tool.d.ts +3 -3
- package/dist/mcp-server/tools/definitions/browse-taxonomy.tool.js +2 -2
- package/dist/mcp-server/tools/definitions/browse-taxonomy.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/compare-products.tool.d.ts +25 -2
- package/dist/mcp-server/tools/definitions/compare-products.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/compare-products.tool.js +77 -22
- package/dist/mcp-server/tools/definitions/compare-products.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-product.tool.d.ts +33 -15
- package/dist/mcp-server/tools/definitions/get-product.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-product.tool.js +23 -2
- package/dist/mcp-server/tools/definitions/get-product.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/index.d.ts +116 -51
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-products.tool.d.ts +28 -4
- package/dist/mcp-server/tools/definitions/search-products.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-products.tool.js +88 -17
- package/dist/mcp-server/tools/definitions/search-products.tool.js.map +1 -1
- package/dist/services/openfoodfacts/openfoodfacts-service.d.ts +46 -9
- package/dist/services/openfoodfacts/openfoodfacts-service.d.ts.map +1 -1
- package/dist/services/openfoodfacts/openfoodfacts-service.js +274 -108
- package/dist/services/openfoodfacts/openfoodfacts-service.js.map +1 -1
- package/dist/services/openfoodfacts/types.d.ts +5 -1
- package/dist/services/openfoodfacts/types.d.ts.map +1 -1
- package/dist/services/taxonomy/taxonomy-service.d.ts.map +1 -1
- package/package.json +11 -10
- package/server.json +3 -3
package/AGENTS.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** openfoodfacts-mcp-server
|
|
4
|
-
**Version:** 0.1.
|
|
5
|
-
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.
|
|
4
|
+
**Version:** 0.1.9
|
|
5
|
+
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.11.0`
|
|
6
6
|
**Engines:** Bun ≥1.3.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
|
|
8
8
|
**Zod:** ^4.4.3
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
- **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters.
|
|
18
18
|
- **Use `ctx.log`** for request-scoped logging. No `console` calls.
|
|
19
19
|
- **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
|
|
20
|
-
- **Check `ctx.elicit
|
|
20
|
+
- **Check `ctx.elicit`** for presence before calling.
|
|
21
21
|
- **Secrets in env vars only** — never hardcoded.
|
|
22
22
|
- **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
|
|
23
23
|
|
|
@@ -96,6 +96,8 @@ export function getServerConfig() {
|
|
|
96
96
|
|
|
97
97
|
`parseEnvConfig` maps Zod schema paths → env var names so errors name the variable (`MY_API_KEY`) not the path (`apiKey`). Throws `ConfigurationError`, which the framework prints as a clean startup banner.
|
|
98
98
|
|
|
99
|
+
For env booleans use `z.stringbool()`, never `z.coerce.boolean()` — `Boolean("false")` is `true`, so a coerced flag can't be disabled through the environment. `z.stringbool()` parses `true/false/1/0/yes/no/on/off` and rejects anything else, so `=false` actually disables.
|
|
100
|
+
|
|
99
101
|
### Server instructions
|
|
100
102
|
|
|
101
103
|
`createApp({ instructions })` — optional server-level orientation, sent to clients on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
|
|
@@ -109,13 +111,14 @@ Handlers receive a unified `ctx` object. Key properties:
|
|
|
109
111
|
| Property | Description |
|
|
110
112
|
|:---------|:------------|
|
|
111
113
|
| `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. |
|
|
112
|
-
| `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
|
|
113
|
-
| `ctx.elicit` | Ask user for structured input. **Check for presence first:** `if (ctx.elicit) { ... }` |
|
|
114
|
-
| `ctx.
|
|
114
|
+
| `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
|
|
115
|
+
| `ctx.elicit` | Ask user for structured input — form call `(message, schema)` or `.url(message, url)` for an external link. **Check for presence first:** `if (ctx.elicit) { ... }` |
|
|
116
|
+
| `ctx.enrich` | Success-path agent context (empty-result notices, query echo, pagination totals) — `ctx.enrich(...)` or `.notice()` / `.total()` / `.echo()` / `.truncated()`. Reaches `structuredContent` and `content[]`; lands only when the definition declares an `enrichment` block (no-op otherwise). |
|
|
117
|
+
| `ctx.content` | Non-text content blocks — `.image(data, mimeType)`, `.audio(data, mimeType)`, or `ctx.content(block)` for a raw block. Prepended to `content[]` after `format()`; never enters `structuredContent`. |
|
|
115
118
|
| `ctx.signal` | `AbortSignal` for cancellation. |
|
|
116
119
|
| `ctx.progress` | Task progress (present when `task: true`) — `.setTotal(n)`, `.increment()`, `.update(message)`. |
|
|
117
120
|
| `ctx.requestId` | Unique request ID. |
|
|
118
|
-
| `ctx.tenantId` | Tenant ID from JWT
|
|
121
|
+
| `ctx.tenantId` | Tenant ID from JWT; `'default'` for stdio or HTTP with auth off. |
|
|
119
122
|
|
|
120
123
|
---
|
|
121
124
|
|
|
@@ -123,7 +126,7 @@ Handlers receive a unified `ctx` object. Key properties:
|
|
|
123
126
|
|
|
124
127
|
Handlers throw — the framework catches, classifies, and formats.
|
|
125
128
|
|
|
126
|
-
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, linter enforces conformance against the handler body. `recovery` is required
|
|
129
|
+
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, linter enforces conformance against the handler body. `recovery` is required (≥ 5 words, lint-validated) — the single source of truth for the agent's next move. Pass `ctx.recoveryFor('reason')` as the throw's data to put it on the wire (`data.recovery.hint`, mirrored into `content[]` text); override with an explicit `{ recovery: { hint: '...' } }` when dynamic runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
|
|
127
130
|
|
|
128
131
|
```ts
|
|
129
132
|
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
@@ -135,7 +138,7 @@ errors: [
|
|
|
135
138
|
],
|
|
136
139
|
async handler(input, ctx) {
|
|
137
140
|
const item = await db.find(input.id);
|
|
138
|
-
if (!item) throw ctx.fail('no_match', `No item ${input.id}
|
|
141
|
+
if (!item) throw ctx.fail('no_match', `No item ${input.id}`, ctx.recoveryFor('no_match'));
|
|
139
142
|
return item;
|
|
140
143
|
}
|
|
141
144
|
```
|
|
@@ -200,7 +203,7 @@ src/
|
|
|
200
203
|
|
|
201
204
|
## Skills
|
|
202
205
|
|
|
203
|
-
Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool.
|
|
206
|
+
Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry.
|
|
204
207
|
|
|
205
208
|
**Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
|
|
206
209
|
|
|
@@ -220,7 +223,6 @@ Available skills:
|
|
|
220
223
|
| `tool-defs-analysis` | Read-only audit of MCP definition language across the surface — voice, leaks, defaults, recovery hints, output descriptions |
|
|
221
224
|
| `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
|
|
222
225
|
| `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
|
|
223
|
-
| `devcheck` | Lint, format, typecheck, audit |
|
|
224
226
|
| `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
|
|
225
227
|
| `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
|
|
226
228
|
| `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
|
|
@@ -228,12 +230,14 @@ Available skills:
|
|
|
228
230
|
| `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
|
|
229
231
|
| `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
|
|
230
232
|
| `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
|
|
233
|
+
| `techniques` | Catalog of response/data-shaping techniques — overflow handling, payload shaping, retrieval patterns |
|
|
231
234
|
| `api-auth` | Auth modes, scopes, JWT/OAuth |
|
|
232
235
|
| `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper for big result sets — Tier 3 opt-in |
|
|
233
236
|
| `api-config` | AppConfig, parseConfig, env vars |
|
|
234
237
|
| `api-context` | Context interface, logger, state, progress |
|
|
235
238
|
| `api-errors` | McpError, JsonRpcErrorCode, error patterns |
|
|
236
239
|
| `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
|
|
240
|
+
| `api-mirror` | MirrorService: persistent self-refreshing local mirror (embedded SQLite + FTS5) of a bulk upstream dataset — Tier 3 opt-in |
|
|
237
241
|
| `api-services` | LLM, Speech, Graph services |
|
|
238
242
|
| `api-testing` | createMockContext, test patterns |
|
|
239
243
|
| `api-utils` | Formatting, parsing, security, pagination, scheduling, telemetry helpers |
|
|
@@ -248,30 +252,33 @@ When you complete a skill's checklist, check the boxes and add a completion time
|
|
|
248
252
|
|
|
249
253
|
## Commands
|
|
250
254
|
|
|
251
|
-
**Runtime:** Scripts use
|
|
255
|
+
**Runtime:** Scripts use Bun's native TypeScript execution — `bun run <cmd>` is the standard invocation. `npm run <cmd>` also works (npm delegates to bun).
|
|
252
256
|
|
|
253
257
|
| Command | Purpose |
|
|
254
258
|
|:--------|:--------|
|
|
255
|
-
| `
|
|
256
|
-
| `
|
|
257
|
-
| `
|
|
258
|
-
| `
|
|
259
|
+
| `bun run build` | Compile TypeScript |
|
|
260
|
+
| `bun run rebuild` | Clean + build |
|
|
261
|
+
| `bun run clean` | Remove build artifacts |
|
|
262
|
+
| `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
|
|
259
263
|
| `bun run audit:refresh` | Delete `bun.lock`, reinstall, and re-run `bun audit`. Use when `devcheck` flags a transitive advisory — Bun's `update` is sticky on transitive resolutions, so the advisory may be a stale-lockfile false positive. If it survives the refresh, it's real. |
|
|
260
|
-
| `
|
|
261
|
-
| `
|
|
262
|
-
| `
|
|
263
|
-
| `
|
|
264
|
-
| `
|
|
265
|
-
| `
|
|
266
|
-
| `
|
|
267
|
-
| `
|
|
268
|
-
| `
|
|
264
|
+
| `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
|
|
265
|
+
| `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity (run by devcheck) |
|
|
266
|
+
| `bun run list-skills` | Print the skill registry |
|
|
267
|
+
| `bun run tree` | Generate directory structure doc |
|
|
268
|
+
| `bun run format` | Auto-fix formatting (safe fixes only) |
|
|
269
|
+
| `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
|
|
270
|
+
| `bun run test` | Run tests (Vitest — use `bun run test`, not `bun test`) |
|
|
271
|
+
| `bun run start:stdio` | Production mode (stdio) |
|
|
272
|
+
| `bun run start:http` | Production mode (HTTP) |
|
|
273
|
+
| `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
|
|
274
|
+
| `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
|
|
275
|
+
| `bun run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
|
|
269
276
|
|
|
270
277
|
---
|
|
271
278
|
|
|
272
279
|
## Bundling
|
|
273
280
|
|
|
274
|
-
`
|
|
281
|
+
`bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
|
|
275
282
|
|
|
276
283
|
**Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match.
|
|
277
284
|
|
|
@@ -289,14 +296,14 @@ Each per-version file opens with YAML frontmatter:
|
|
|
289
296
|
---
|
|
290
297
|
summary: "One-line headline, ≤350 chars" # required — powers the rollup index
|
|
291
298
|
breaking: false # optional — true flags breaking changes
|
|
292
|
-
security: false # optional — true
|
|
299
|
+
security: false # optional — true ONLY for a source-code security fix, never a dependency CVE bump
|
|
293
300
|
---
|
|
294
301
|
|
|
295
302
|
# 0.1.0 — YYYY-MM-DD
|
|
296
303
|
...
|
|
297
304
|
```
|
|
298
305
|
|
|
299
|
-
`breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section. When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
|
|
306
|
+
`breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section — set it only for a security fix in this server's *own source code*, never for a routine dependency or transitive CVE bump (record those under `## Dependencies`). When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
|
|
300
307
|
|
|
301
308
|
`agent-notes` is an optional free-form field for maintenance agents processing the release downstream. Content here won't appear in the rendered CHANGELOG — it's consumed by agents running the `maintenance` skill. Use it for adoption instructions that don't fit the human-facing sections: new files to create, fields to populate, one-time migration steps. Omit entirely when there's nothing to say.
|
|
302
309
|
|
package/CLAUDE.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** openfoodfacts-mcp-server
|
|
4
|
-
**Version:** 0.1.
|
|
5
|
-
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.
|
|
4
|
+
**Version:** 0.1.9
|
|
5
|
+
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.11.0`
|
|
6
6
|
**Engines:** Bun ≥1.3.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
|
|
8
8
|
**Zod:** ^4.4.3
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
- **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters.
|
|
18
18
|
- **Use `ctx.log`** for request-scoped logging. No `console` calls.
|
|
19
19
|
- **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
|
|
20
|
-
- **Check `ctx.elicit
|
|
20
|
+
- **Check `ctx.elicit`** for presence before calling.
|
|
21
21
|
- **Secrets in env vars only** — never hardcoded.
|
|
22
22
|
- **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
|
|
23
23
|
|
|
@@ -96,6 +96,8 @@ export function getServerConfig() {
|
|
|
96
96
|
|
|
97
97
|
`parseEnvConfig` maps Zod schema paths → env var names so errors name the variable (`MY_API_KEY`) not the path (`apiKey`). Throws `ConfigurationError`, which the framework prints as a clean startup banner.
|
|
98
98
|
|
|
99
|
+
For env booleans use `z.stringbool()`, never `z.coerce.boolean()` — `Boolean("false")` is `true`, so a coerced flag can't be disabled through the environment. `z.stringbool()` parses `true/false/1/0/yes/no/on/off` and rejects anything else, so `=false` actually disables.
|
|
100
|
+
|
|
99
101
|
### Server instructions
|
|
100
102
|
|
|
101
103
|
`createApp({ instructions })` — optional server-level orientation, sent to clients on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
|
|
@@ -109,13 +111,14 @@ Handlers receive a unified `ctx` object. Key properties:
|
|
|
109
111
|
| Property | Description |
|
|
110
112
|
|:---------|:------------|
|
|
111
113
|
| `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. |
|
|
112
|
-
| `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
|
|
113
|
-
| `ctx.elicit` | Ask user for structured input. **Check for presence first:** `if (ctx.elicit) { ... }` |
|
|
114
|
-
| `ctx.
|
|
114
|
+
| `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
|
|
115
|
+
| `ctx.elicit` | Ask user for structured input — form call `(message, schema)` or `.url(message, url)` for an external link. **Check for presence first:** `if (ctx.elicit) { ... }` |
|
|
116
|
+
| `ctx.enrich` | Success-path agent context (empty-result notices, query echo, pagination totals) — `ctx.enrich(...)` or `.notice()` / `.total()` / `.echo()` / `.truncated()`. Reaches `structuredContent` and `content[]`; lands only when the definition declares an `enrichment` block (no-op otherwise). |
|
|
117
|
+
| `ctx.content` | Non-text content blocks — `.image(data, mimeType)`, `.audio(data, mimeType)`, or `ctx.content(block)` for a raw block. Prepended to `content[]` after `format()`; never enters `structuredContent`. |
|
|
115
118
|
| `ctx.signal` | `AbortSignal` for cancellation. |
|
|
116
119
|
| `ctx.progress` | Task progress (present when `task: true`) — `.setTotal(n)`, `.increment()`, `.update(message)`. |
|
|
117
120
|
| `ctx.requestId` | Unique request ID. |
|
|
118
|
-
| `ctx.tenantId` | Tenant ID from JWT
|
|
121
|
+
| `ctx.tenantId` | Tenant ID from JWT; `'default'` for stdio or HTTP with auth off. |
|
|
119
122
|
|
|
120
123
|
---
|
|
121
124
|
|
|
@@ -123,7 +126,7 @@ Handlers receive a unified `ctx` object. Key properties:
|
|
|
123
126
|
|
|
124
127
|
Handlers throw — the framework catches, classifies, and formats.
|
|
125
128
|
|
|
126
|
-
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, linter enforces conformance against the handler body. `recovery` is required
|
|
129
|
+
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, linter enforces conformance against the handler body. `recovery` is required (≥ 5 words, lint-validated) — the single source of truth for the agent's next move. Pass `ctx.recoveryFor('reason')` as the throw's data to put it on the wire (`data.recovery.hint`, mirrored into `content[]` text); override with an explicit `{ recovery: { hint: '...' } }` when dynamic runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
|
|
127
130
|
|
|
128
131
|
```ts
|
|
129
132
|
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
@@ -135,7 +138,7 @@ errors: [
|
|
|
135
138
|
],
|
|
136
139
|
async handler(input, ctx) {
|
|
137
140
|
const item = await db.find(input.id);
|
|
138
|
-
if (!item) throw ctx.fail('no_match', `No item ${input.id}
|
|
141
|
+
if (!item) throw ctx.fail('no_match', `No item ${input.id}`, ctx.recoveryFor('no_match'));
|
|
139
142
|
return item;
|
|
140
143
|
}
|
|
141
144
|
```
|
|
@@ -200,7 +203,7 @@ src/
|
|
|
200
203
|
|
|
201
204
|
## Skills
|
|
202
205
|
|
|
203
|
-
Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool.
|
|
206
|
+
Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry.
|
|
204
207
|
|
|
205
208
|
**Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
|
|
206
209
|
|
|
@@ -220,7 +223,6 @@ Available skills:
|
|
|
220
223
|
| `tool-defs-analysis` | Read-only audit of MCP definition language across the surface — voice, leaks, defaults, recovery hints, output descriptions |
|
|
221
224
|
| `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
|
|
222
225
|
| `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
|
|
223
|
-
| `devcheck` | Lint, format, typecheck, audit |
|
|
224
226
|
| `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
|
|
225
227
|
| `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
|
|
226
228
|
| `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
|
|
@@ -228,12 +230,14 @@ Available skills:
|
|
|
228
230
|
| `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
|
|
229
231
|
| `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
|
|
230
232
|
| `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
|
|
233
|
+
| `techniques` | Catalog of response/data-shaping techniques — overflow handling, payload shaping, retrieval patterns |
|
|
231
234
|
| `api-auth` | Auth modes, scopes, JWT/OAuth |
|
|
232
235
|
| `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper for big result sets — Tier 3 opt-in |
|
|
233
236
|
| `api-config` | AppConfig, parseConfig, env vars |
|
|
234
237
|
| `api-context` | Context interface, logger, state, progress |
|
|
235
238
|
| `api-errors` | McpError, JsonRpcErrorCode, error patterns |
|
|
236
239
|
| `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
|
|
240
|
+
| `api-mirror` | MirrorService: persistent self-refreshing local mirror (embedded SQLite + FTS5) of a bulk upstream dataset — Tier 3 opt-in |
|
|
237
241
|
| `api-services` | LLM, Speech, Graph services |
|
|
238
242
|
| `api-testing` | createMockContext, test patterns |
|
|
239
243
|
| `api-utils` | Formatting, parsing, security, pagination, scheduling, telemetry helpers |
|
|
@@ -248,30 +252,33 @@ When you complete a skill's checklist, check the boxes and add a completion time
|
|
|
248
252
|
|
|
249
253
|
## Commands
|
|
250
254
|
|
|
251
|
-
**Runtime:** Scripts use
|
|
255
|
+
**Runtime:** Scripts use Bun's native TypeScript execution — `bun run <cmd>` is the standard invocation. `npm run <cmd>` also works (npm delegates to bun).
|
|
252
256
|
|
|
253
257
|
| Command | Purpose |
|
|
254
258
|
|:--------|:--------|
|
|
255
|
-
| `
|
|
256
|
-
| `
|
|
257
|
-
| `
|
|
258
|
-
| `
|
|
259
|
+
| `bun run build` | Compile TypeScript |
|
|
260
|
+
| `bun run rebuild` | Clean + build |
|
|
261
|
+
| `bun run clean` | Remove build artifacts |
|
|
262
|
+
| `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
|
|
259
263
|
| `bun run audit:refresh` | Delete `bun.lock`, reinstall, and re-run `bun audit`. Use when `devcheck` flags a transitive advisory — Bun's `update` is sticky on transitive resolutions, so the advisory may be a stale-lockfile false positive. If it survives the refresh, it's real. |
|
|
260
|
-
| `
|
|
261
|
-
| `
|
|
262
|
-
| `
|
|
263
|
-
| `
|
|
264
|
-
| `
|
|
265
|
-
| `
|
|
266
|
-
| `
|
|
267
|
-
| `
|
|
268
|
-
| `
|
|
264
|
+
| `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
|
|
265
|
+
| `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity (run by devcheck) |
|
|
266
|
+
| `bun run list-skills` | Print the skill registry |
|
|
267
|
+
| `bun run tree` | Generate directory structure doc |
|
|
268
|
+
| `bun run format` | Auto-fix formatting (safe fixes only) |
|
|
269
|
+
| `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
|
|
270
|
+
| `bun run test` | Run tests (Vitest — use `bun run test`, not `bun test`) |
|
|
271
|
+
| `bun run start:stdio` | Production mode (stdio) |
|
|
272
|
+
| `bun run start:http` | Production mode (HTTP) |
|
|
273
|
+
| `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
|
|
274
|
+
| `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
|
|
275
|
+
| `bun run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
|
|
269
276
|
|
|
270
277
|
---
|
|
271
278
|
|
|
272
279
|
## Bundling
|
|
273
280
|
|
|
274
|
-
`
|
|
281
|
+
`bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
|
|
275
282
|
|
|
276
283
|
**Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match.
|
|
277
284
|
|
|
@@ -289,14 +296,14 @@ Each per-version file opens with YAML frontmatter:
|
|
|
289
296
|
---
|
|
290
297
|
summary: "One-line headline, ≤350 chars" # required — powers the rollup index
|
|
291
298
|
breaking: false # optional — true flags breaking changes
|
|
292
|
-
security: false # optional — true
|
|
299
|
+
security: false # optional — true ONLY for a source-code security fix, never a dependency CVE bump
|
|
293
300
|
---
|
|
294
301
|
|
|
295
302
|
# 0.1.0 — YYYY-MM-DD
|
|
296
303
|
...
|
|
297
304
|
```
|
|
298
305
|
|
|
299
|
-
`breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section. When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
|
|
306
|
+
`breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section — set it only for a security fix in this server's *own source code*, never for a routine dependency or transitive CVE bump (record those under `## Dependencies`). When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
|
|
300
307
|
|
|
301
308
|
`agent-notes` is an optional free-form field for maintenance agents processing the release downstream. Content here won't appear in the rendered CHANGELOG — it's consumed by agents running the `maintenance` skill. Use it for adoption instructions that don't fit the human-facing sections: new files to create, fields to populate, one-time migration steps. Omit entirely when there's nothing to say.
|
|
302
309
|
|
package/Dockerfile
CHANGED
|
@@ -4,15 +4,17 @@
|
|
|
4
4
|
# This stage installs all dependencies (including dev), builds the TypeScript
|
|
5
5
|
# source code into JavaScript, and prepares the production assets.
|
|
6
6
|
# ==============================================================================
|
|
7
|
-
FROM oven/bun:1.3 AS build
|
|
7
|
+
FROM oven/bun:1.3.14 AS build
|
|
8
8
|
|
|
9
9
|
WORKDIR /usr/src/app
|
|
10
10
|
|
|
11
11
|
# Copy dependency manifests for optimized layer caching
|
|
12
12
|
COPY package.json bun.lock ./
|
|
13
13
|
|
|
14
|
-
# Install all dependencies (including dev dependencies for building)
|
|
15
|
-
|
|
14
|
+
# Install all dependencies (including dev dependencies for building).
|
|
15
|
+
# The BuildKit cache mount persists Bun's global package cache across builds.
|
|
16
|
+
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
17
|
+
bun install --frozen-lockfile --ignore-scripts
|
|
16
18
|
|
|
17
19
|
# Copy the rest of the source code
|
|
18
20
|
COPY . .
|
|
@@ -28,7 +30,7 @@ RUN bun run build
|
|
|
28
30
|
# application. It uses a slim base image and only includes production
|
|
29
31
|
# dependencies and build artifacts.
|
|
30
32
|
# ==============================================================================
|
|
31
|
-
FROM oven/bun:1.3-slim AS production
|
|
33
|
+
FROM oven/bun:1.3.14-slim AS production
|
|
32
34
|
|
|
33
35
|
WORKDIR /usr/src/app
|
|
34
36
|
|
|
@@ -51,14 +53,16 @@ COPY package.json bun.lock ./
|
|
|
51
53
|
|
|
52
54
|
# Install only production dependencies, ignoring any lifecycle scripts (like 'prepare')
|
|
53
55
|
# that are not needed in the final production image.
|
|
54
|
-
RUN bun
|
|
56
|
+
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
57
|
+
bun install --production --frozen-lockfile --ignore-scripts
|
|
55
58
|
|
|
56
59
|
# Conditionally install OpenTelemetry optional peer dependencies (Tier 3).
|
|
57
60
|
# These are not bundled by default to keep the base image lean. Enable at build time
|
|
58
61
|
# with: docker build --build-arg OTEL_ENABLED=true
|
|
59
62
|
ARG OTEL_ENABLED=true
|
|
60
|
-
RUN
|
|
61
|
-
|
|
63
|
+
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
64
|
+
if [ "$OTEL_ENABLED" = "true" ]; then \
|
|
65
|
+
bun add --omit=dev --ignore-scripts @hono/otel \
|
|
62
66
|
@opentelemetry/instrumentation-http \
|
|
63
67
|
@opentelemetry/exporter-metrics-otlp-http \
|
|
64
68
|
@opentelemetry/exporter-trace-otlp-http \
|
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
|
|
|
@@ -56,11 +56,13 @@ Search Open Food Facts by text and/or structured tag filters.
|
|
|
56
56
|
|
|
57
57
|
- Full-text search across product names, brands, and ingredients
|
|
58
58
|
- Structured filters: `categories_tag`, `brands_tag`, `labels_tag`, `nutrition_grade` (a–e), `nova_group` (1–4), `countries_tag`
|
|
59
|
+
- Text query and tag filters combine — a query with filters returns products that match the text *and* satisfy every filter (e.g., `query: "dark chocolate"` + `labels_tag: en:organic` + `countries_tag: en:france`)
|
|
59
60
|
- All filter values are canonical tag IDs — use `off_browse_taxonomy` to resolve human terms (e.g., "organic" → `en:organic`)
|
|
60
61
|
- Pagination via `page` (1-based) and `page_size` (1–50, default 20); response includes `total` count for computing total pages
|
|
62
|
+
- Searches carrying a text query serve only the first 10,000 results — a deeper `page * page_size` is rejected up front with the highest reachable page, not sent and retried. Tag-only searches publish no window, but deep pages are refused unpredictably, so narrowing the filters beats paging far in
|
|
61
63
|
- Returns summary rows (barcode, name, brand, Nutri-Score, NOVA, categories) — use `off_get_product` for full label data
|
|
62
64
|
- Result counts reflect contributed products, not total products on the market
|
|
63
|
-
- Search
|
|
65
|
+
- Search limited to ~10 requests/min by this server's own client-side budget, kept well inside what Open Food Facts asks of clients
|
|
64
66
|
|
|
65
67
|
---
|
|
66
68
|
|
|
@@ -68,10 +70,11 @@ Search Open Food Facts by text and/or structured tag filters.
|
|
|
68
70
|
|
|
69
71
|
Side-by-side nutrition and scoring comparison for 2–10 barcodes.
|
|
70
72
|
|
|
71
|
-
-
|
|
73
|
+
- Accepts 2–10 barcodes, compared in the order provided
|
|
72
74
|
- Returns a normalized comparison table: energy (kcal/100g), fat, saturated fat, sugars, salt, protein, fiber, Nutri-Score, NOVA group, and Green-Score
|
|
73
75
|
- Missing nutrition data is preserved as `null` — comparisons are not imputed or estimated
|
|
74
76
|
- `not_found` list identifies barcodes with no contributor record (partial results are not an error)
|
|
77
|
+
- `failed` list identifies barcodes whose fetch failed, with the per-barcode reason — kept separate from `not_found`, which claims the opposite. A failed barcode never blocks the rows that resolved
|
|
75
78
|
|
|
76
79
|
---
|
|
77
80
|
|
|
@@ -81,8 +84,8 @@ Browse the canonical Open Food Facts tag vocabulary before building `off_search_
|
|
|
81
84
|
|
|
82
85
|
- Facets: `categories`, `labels`, `allergens`, `additives`, `countries`, `nova_groups`, `nutrition_grades`
|
|
83
86
|
- Optional `search` parameter: case-insensitive substring match against tag ID or display name (e.g., `"gluten"` → `en:gluten`, `en:no-gluten`, `en:no-added-gluten`)
|
|
84
|
-
- Taxonomy is
|
|
85
|
-
-
|
|
87
|
+
- Taxonomy is served from a curated in-process vocabulary — no live network call, so lookups are instant and offline
|
|
88
|
+
- The categories facet is broad — pass a `search` term to narrow it to the relevant tags
|
|
86
89
|
- `limit` controls results returned (1–100, default 20)
|
|
87
90
|
|
|
88
91
|
---
|
|
@@ -101,8 +104,8 @@ Built on [`@cyanheads/mcp-ts-core`](https://www.npmjs.com/package/@cyanheads/mcp
|
|
|
101
104
|
Open Food Facts-specific:
|
|
102
105
|
|
|
103
106
|
- No API key required — the identifying `User-Agent` header (required by OFF terms) is baked into the service layer
|
|
104
|
-
- Token-bucket rate limiting per endpoint class: product reads (~100/min), search (~10/min)
|
|
105
|
-
- Automatic retry (3 attempts, 500ms base) with HTML error page detection for 503 during high load
|
|
107
|
+
- Token-bucket rate limiting per endpoint class: product reads (~100/min), search (~10/min). A local refusal says so — it never reports itself as an Open Food Facts rate limit
|
|
108
|
+
- Automatic retry (3 attempts, 500ms base) for transient failures only — 5xx, timeouts, and 429 (honoring `Retry-After`), with HTML error page detection for 503 during high load. A 4xx is never retried; the upstream's own explanation is surfaced instead
|
|
106
109
|
- Nutriments normalized from raw hyphenated keys (`energy-kcal_100g`) to underscore form — only the `_100g` and `_serving` variants are returned
|
|
107
110
|
- Embedded tag taxonomy for `off_browse_taxonomy` — curated 200+ category subset, full allergen/label/additive vocabularies
|
|
108
111
|
|
|
@@ -111,7 +114,8 @@ Agent-friendly output:
|
|
|
111
114
|
- `found` field on every product response — explicit `false` when a barcode has no contributor record, not a thrown error
|
|
112
115
|
- Missing fields signal incomplete crowd-sourced data, not product attribute absence — surfaced in descriptions and format output
|
|
113
116
|
- Computed scores (Nutri-Score, NOVA, Green-Score) returned as-is with regional caveat notes — not interpreted or normalized to health claims
|
|
114
|
-
- `not_found` list in `off_compare_products` allows partial batch comparisons without request failure
|
|
117
|
+
- `not_found` list in `off_compare_products` allows partial batch comparisons without request failure — and a barcode whose fetch failed lands in `failed` instead, so a transport error is never reported as "no contributor record"
|
|
118
|
+
- Every failure carries a declared `reason` and a recovery hint on both client surfaces — timeouts, upstream outages, upstream rejections, and rate limits each resolve to their own error code and their own next step
|
|
115
119
|
|
|
116
120
|
## Getting started
|
|
117
121
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Combine free-text queries with tag filters in off_search_products via a single search-a-licious Lucene query, escape free text against injection, rewrite tool descriptions to drop implementation details, and derive USER_AGENT from package.json."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.1.8 — 2026-07-05
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **`off_search_products`** — a text `query` now combines with tag filters instead of the two being mutually exclusive; `buildTextSearchQuery` folds recognized filters (`categories_tag`, `brands_tag`, `labels_tag`, `nutrition_grade`, `nova_group`, `countries_tag`) into one search-a-licious Lucene `q` alongside the free text. Score/NOVA filters use different field names on this path (`nutriscore_grade`, `nova_group`, bare values, no `_tags` suffix) than the tag-only `/api/v2/search` route (`nutrition_grades_tags`, `nova_groups_tags`). ([#6](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/6))
|
|
12
|
+
|
|
13
|
+
## Changed
|
|
14
|
+
|
|
15
|
+
- **Tool descriptions** — `off_search_products`, `off_compare_products`, and `off_browse_taxonomy` descriptions rewritten to drop backend implementation details (mutually-exclusive routing language, "fetched in parallel," the taxonomy's anonymous-bot-client access rationale, directive `limit` phrasing) in favor of user-facing contract text; mirrored in `README.md` and `docs/design.md`. ([#7](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/7))
|
|
16
|
+
- **`docs/design.md`** — stale field-name reference corrected: `nutrition_grades`/`nova_groups` → `nutrition_grades_tags`/`nova_groups_tags` (the bare keys are silently ignored by `/api/v2/search`; documentation-only, matches existing shipped behavior).
|
|
17
|
+
|
|
18
|
+
## Fixed
|
|
19
|
+
|
|
20
|
+
- **Lucene query injection** — free-text terms passed to `off_search_products` are now escaped (`escapeLuceneQueryText`) before being folded into the Lucene `q`; previously an unescaped reserved character (e.g. `query: "brands: nutella"`) was parsed as a `field:value` filter clause instead of literal search text.
|
|
21
|
+
- **`USER_AGENT` version drift** — read from `package.json` at load instead of a hardcoded string, so the header sent to Open Food Facts can no longer fall behind the shipped release. ([#8](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/8))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Declare typed reasons (upstream_error, upstream_timeout, upstream_rejected, rate_limited) across the service layer, split off_compare_products failures from not_found, pre-flight the text-search 10k-result window, and adopt mcp-ts-core ^0.11.0."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.1.9 — 2026-07-26
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **`off_search_products`** — a `page_out_of_range` validation error rejects `page * page_size` beyond the text backend's 10,000-result window before the request is sent, naming the highest reachable page instead of surfacing a retried upstream failure. ([#19](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/19))
|
|
12
|
+
- **`off_compare_products`** — a `failed` output array reports barcodes whose fetch failed, distinct from `not_found` (confirmed no contributor record). A mixed batch keeps the rows that resolved instead of aborting or misreporting the failure as "not found." ([#11](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/11))
|
|
13
|
+
- **`upstream_timeout`, `upstream_rejected`, `rate_limited`** — declared on `off_get_product`, `off_search_products`, and `off_compare_products`, alongside the existing `upstream_error`. Every service-layer failure now carries `data.reason` and `data.recovery.hint` on both client surfaces. ([#12](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/12))
|
|
14
|
+
|
|
15
|
+
## Changed
|
|
16
|
+
|
|
17
|
+
- **`openfoodfacts-service.ts`** — every fetch site now goes through `fetchWithTimeout`/`ctx.recoveryFor`, replacing the hand-rolled status ladder. A 4xx is classified `upstream_rejected` and is not retried (previously retried 4x as `ServiceUnavailable`); the upstream's own `detail` body is surfaced in the message. 5xx, timeouts, and 429 remain retryable.
|
|
18
|
+
- **`RateLimiter.check()`** — takes `ctx` and raises `rate_limited` naming this server's own budget and the seconds until a slot frees, rather than attributing the refusal to Open Food Facts.
|
|
19
|
+
- **`off_search_products` truncation guidance** — counts pages the backend will actually serve (capped at the 10k-result window on the text path) instead of the total implied by the match count.
|
|
20
|
+
|
|
21
|
+
## Fixed
|
|
22
|
+
|
|
23
|
+
- **`off_compare_products`** — a rejected settlement is no longer reclassified as `not_found` when its error message lacks the literal string "Open Food Facts." Classification is by settlement state, never message text. ([#11](https://github.com/cyanheads/openfoodfacts-mcp-server/issues/11))
|
|
24
|
+
|
|
25
|
+
## Dependencies
|
|
26
|
+
|
|
27
|
+
- `@cyanheads/mcp-ts-core` `^0.10.12` → `^0.11.0`
|
|
28
|
+
- `typescript` `^6.0.3` → `^7.0.2`
|
|
29
|
+
- `@biomejs/biome` `^2.5.0` → `^2.5.5`
|
|
30
|
+
- `@types/node` `^26.0.0` → `^26.1.1`
|
|
31
|
+
- `ignore` `^7.0.5` → `^7.0.6`
|
|
32
|
+
- `tsc-alias` `^1.8.17` → `^1.9.1`
|
|
33
|
+
- `vitest` `^4.1.9` → `^4.1.10`
|
|
34
|
+
- `@socketsecurity/bun-security-scanner` `^1.1.2` added (Bun install-time malware/typosquat/CVE scanning, configured in `bunfig.toml`)
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
6
|
export declare const offBrowseTaxonomyTool: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
7
7
|
facet: z.ZodEnum<{
|
|
8
|
-
categories: "categories";
|
|
9
|
-
labels: "labels";
|
|
10
|
-
allergens: "allergens";
|
|
11
8
|
additives: "additives";
|
|
9
|
+
allergens: "allergens";
|
|
10
|
+
categories: "categories";
|
|
12
11
|
countries: "countries";
|
|
12
|
+
labels: "labels";
|
|
13
13
|
nova_groups: "nova_groups";
|
|
14
14
|
nutrition_grades: "nutrition_grades";
|
|
15
15
|
}>;
|
|
@@ -6,7 +6,7 @@ import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
|
6
6
|
import { getTaxonomyService } from '../../../services/taxonomy/taxonomy-service.js';
|
|
7
7
|
export const offBrowseTaxonomyTool = tool('off_browse_taxonomy', {
|
|
8
8
|
title: 'Browse Food Facts Taxonomy',
|
|
9
|
-
description: 'Browse and search the canonical tag vocabulary for Open Food Facts filter facets. Returns tag IDs and display names for use as filter values in off_search_products. Covers categories, labels/certifications, allergens, additives, countries, NOVA groups, and Nutri-Score grades.
|
|
9
|
+
description: 'Browse and search the canonical tag vocabulary for Open Food Facts filter facets. Returns tag IDs and display names for use as filter values in off_search_products. Covers categories, labels/certifications, allergens, additives, countries, NOVA groups, and Nutri-Score grades. Tag IDs use the "en:" prefix convention (e.g. "en:organic", "en:gluten-free", "en:milk"). Use these tag IDs as filter values, not plain English terms.',
|
|
10
10
|
annotations: {
|
|
11
11
|
readOnlyHint: true,
|
|
12
12
|
idempotentHint: true,
|
|
@@ -34,7 +34,7 @@ export const offBrowseTaxonomyTool = tool('off_browse_taxonomy', {
|
|
|
34
34
|
.min(1)
|
|
35
35
|
.max(100)
|
|
36
36
|
.default(20)
|
|
37
|
-
.describe('Maximum entries to return (1–100, default 20).
|
|
37
|
+
.describe('Maximum entries to return (1–100, default 20). The categories facet is broad; a search term narrows it to the relevant tags.'),
|
|
38
38
|
}),
|
|
39
39
|
output: z.object({
|
|
40
40
|
facet: z.string().describe('The facet name that was queried (echoes the input).'),
|
|
@@ -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;AAEzF,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC,qBAAqB,EAAE;IAC/D,KAAK,EAAE,4BAA4B;IACnC,WAAW,EACT,
|
|
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;AAEzF,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC,qBAAqB,EAAE;IAC/D,KAAK,EAAE,4BAA4B;IACnC,WAAW,EACT,6aAA6a;IAC/a,WAAW,EAAE;QACX,YAAY,EAAE,IAAI;QAClB,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,KAAK;KACrB;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,2WAA2W,CAC5W;QACH,MAAM,EAAE,CAAC;aACN,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,4LAA4L,CAC7L;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,8HAA8H,CAC/H;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,gGAAgG,CACjG;YACH,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;YAC1E,QAAQ,EAAE,CAAC;iBACR,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CACP,4EAA4E,CAC7E;SACJ,CAAC;aACD,QAAQ,CAAC,qEAAqE,CAAC,CACnF;aACA,QAAQ,CAAC,uBAAuB,CAAC;QACpC,cAAc,EAAE,CAAC;aACd,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,4EAA4E,CAAC;KAC1F,CAAC;IAEF,UAAU,EAAE;QACV,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,OAAO,CAAC,KAAK,EAAE,GAAG;QAChB,MAAM,GAAG,GAAG,kBAAkB,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CACvB,KAAK,CAAC,KAAc,EACpB,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,EAChF,KAAK,CAAC,KAAK,CACZ,CAAC;QAEF,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,KAAK,EAAE,MAAM,CAAC,cAAc;SAC7B,CAAC,CAAC;QAEH,qFAAqF;QACrF,yFAAyF;QACzF,sFAAsF;QACtF,+BAA+B;QAC/B,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC;YACjD,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;QACxE,CAAC;QAED,OAAO;YACL,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,cAAc,EAAE,MAAM,CAAC,cAAc;SACtC,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,KAAK,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;YACnE,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,MAAM,QAAQ,GACZ,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;YACpF,KAAK,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,EAAE,QAAQ,GAAG,CAAC,IAAI,GAAG,QAAQ,EAAE,CAAC,CAAC;QACzD,CAAC;QAED,KAAK,CAAC,IAAI,CACR,0FAA0F,CAC3F,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"}
|