Skip to content

openfda mcp server AGENTS.md changelog

io.github.cyanheads/openfda-mcp-server

4 changes recorded. MCP Harbor re-reads this listing from the official registry every six hours and asks the live server what it exposes, and keeps what changed between one look and the next.

  • AGENTS.md +13 −9

    https://raw.githubusercontent.com/cyanheads/openfda-mcp-server/HEAD/AGENTS.md

    − **Version:** 0.9.1− **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.18`− **Engines:** Bun ≥1.4.0, Node ≥24.0.0−     limit: z.number().min(1).max(1000).default(100).optional().describe('Top terms to return'),−     const response = await svc.query(input.endpoint, { search: input.search, count: input.count, limit: input.limit }, ctx);− **Service-layer throws carry `data: { reason }`, and their contract entries carry `thrownBy: 'service'`.** The conformance lint scans handler source only — failures thrown from `openfda-service.ts` aren't visible to it. To make service throws carry the same wire-shape `error.data.reason` clients see from `ctx.fail`, the service passes `data: { reason: 'X' }` to the factory (used in `openfda-service.ts` for `rate_limited`, `upstream_error`, `pagination_limit_reached`, `query_error`, `not_aggregat−       page-budget.ts                    # Serialized-byte budget for inline search pages + over-budget disclosure−       drug-event-codes.ts               # Code tables for coded drug/event fields (route, outcome, age group), shared by format() and the field catalog−       schema-utils.ts                   # Shared input schemas + handler guards (skip ceiling, search delimiters, `+` separator normalization)+ **Version:** 0.10.0+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.22`+ **Engines:** Bun ≥1.4.4, Node ≥24.0.0+ import { pageLimit, resolvePageSize } from '@/mcp-server/tools/schema-utils.js';+     limit: pageLimit('count', 'Top terms to return.'),+     const { limit } = resolvePageSize(input.limit, 'count', ctx);+     const response = await svc.query(input.endpoint, { search: input.search, count: input.count, limit }, ctx);+ **Service-layer throws carry `data: { reason }`, and their contract entries carry `thrownBy: 'service'`.** The conformance lint scans handler source only — failures thrown from `openfda-service.ts` aren't visible to it. To make service throws carry the same wire-shape `error.data.reason` clients see from `ctx.fail`, the service passes `data: { reason: 'X' }` to the factory (used in `openfda-service.ts` for `rate_limited`, `upstream_error`, `pagination_limit_reached`, `query_error`, `not_aggregat+       page-budget.ts                    # Oversized-record disclosure for inline search pages+       drug-event-codes.ts               # Code tables for coded drug/event fields (route, outcome, age group, onset-age unit, per-drug action, dechallenge, rechallenge, duration, interval, and dose units, report type, reporter, sender, receiver, and expedited codes) and `withCodeLabels`, shared by format() and the field catalog+       default-sort.ts                   # Newest-first default sort for a bare search-tool browse+       drug-profile-sections.ts          # openfda_drug_profile selection logic: Drugs@FDA application ranking, approval and shortage sections+       schema-utils.ts                   # Shared input schemas + handler guards (per-endpoint page sizes and the limit clamp, skip ceiling, search delimiters, `+` separator normalization)

  • AGENTS.md +21 −10

    https://raw.githubusercontent.com/cyanheads/openfda-mcp-server/HEAD/AGENTS.md

    − **Version:** 0.8.1− **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.15`− **MCP SDK:** `@modelcontextprotocol/server` ^2.3.1 (via the framework)− **Zod:** ^4.6.5−       format-utils.ts                   # Shared format() helpers−       schema-utils.ts                   # Shared input schemas + handler guards (skip ceiling, search delimiters)− Skills are modular instructions in `framework-skills/` at the project root. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. The directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand these development skills to every agent that installs it. Keep `skills/` free for skills meant f− **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `framework-skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.− | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity (run by devcheck) |− `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. MCPB is stdio-only — HTTP deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.+ **Version:** 0.9.1+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.18`+ **MCP SDK:** `@modelcontextprotocol/server` ^2.4.0 (via the framework)+ **Zod:** ^4.6.7+       drug-event-codes.ts               # Code tables for coded drug/event fields (route, outcome, age group), shared by format() and the field catalog+       format-utils.ts                   # Shared format() helpers + codeLiteral (caller text in notices and format() prose)+       schema-utils.ts                   # Shared input schemas + handler guards (skip ceiling, search delimiters, `+` separator normalization)+ Two skill trees live at the project root, written for different readers:+ | Tree | For | Written by |+ |:-----|:----|:-----------|+ | `framework-skills/` | The agent developing this server — framework skills (`metadata.audience: external`) plus any development-time skills of the server's own | `init` and the `maintenance` sync (framework skills); you (your own) |+ | `skills/` | The agents that install the server and call its tools — tool chains, identifier sources, gates | You, deliberately, with the `add-skill` skill. Absent by default; usually none is needed |+ Development skills are modular instructions. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. Their directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand them to every agent that installs it.+ A published skill in root `skills/` reaches plugin installs and the `.mcpb` bundle as-is, ships in the npm package and the Docker image, and reaches MCP clients when the server opts in with `createApp({ skills: 'skills' })`. `lint:packaging` validates the tree.+ **Agent skill directory:** Copy `framework-skills/` into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `framework-skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.+ | `add-skill` | Author a skill the server publishes in root `skills/` for the agents that use it — when one earns its place, structure, validation, delivery |+ | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, `.mcpbignore` coverage, plugin manifests, the published `skills/` tree (run by devcheck) |+ `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. `mcpb pack` reads `.mcpbignore`, not `.gitignore`, so every root path the bundle must not ship — source, tests, docs, caches, logs, `data/` (the local mirror), agent docs, plugin manifests, gitignored local files — needs a root-anchored line there (`/docs/`, never `docs/`, which also strips `node_modules/*/docs/`); `lint:packaging` names each missing or unanchored line. Root `skills/` ships on purpose. + - [ ] `plugin.json` + `mcp.json` (Agent Plugins 1.0.0 — read by Codex, Cursor, GitHub Copilot, VS Code, Kiro) populated — `plugin.json` `name` = the unscoped repo name (lowercase `a-z`, `0-9`, `-`, `.` only); `version`, `description`, `author`, `homepage`, `repository`, `license`, `keywords` from `package.json`; no other top-level field, `extensions` included. `mcp.json` holds one `"type": "stdio"` entry keyed by the unscoped name, `npx -y <full package name>`, and the same fixed `env` as `.code+ - [ ] Any new root dev path has a root-anchored line in `.mcpbignore`; `bun run bundle` and `lint:packaging` pass+ - [ ] If the server publishes a skill in root `skills/`: `lint:packaging` passes, and `field-test` verified every gate and identifier claim in it against live tool behavior

  • AGENTS.md +7 −6

    https://raw.githubusercontent.com/cyanheads/openfda-mcp-server/HEAD/AGENTS.md

    − **Version:** 0.8.0− **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.11`− **MCP SDK:** `@modelcontextprotocol/server` ^2.2.0 (via the framework)−       page-budget.ts                    # Serialized-byte budget for inline search pages− | `bun run bundle` | Build and pack as `.mcpb` for one-click Claude Desktop install |− **Every release goes through a gated release PR** — `git-wrapup`'s "Release PR mode", mode `gated`. Three separate runs, never one: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the changelog entry plus a gates section); `release-pr-review` reviews and fixes on that branch (each fix an ordinary commit on top of the stack, pushed plainly — nothing already pushed is ever rewritten, so `main` keeps the record of w+ **Version:** 0.8.1+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.15`+ **MCP SDK:** `@modelcontextprotocol/server` ^2.3.1 (via the framework)+       page-budget.ts                    # Serialized-byte budget for inline search pages + over-budget disclosure+       record-projection.ts              # `fields` projection of inline search records to dotted paths+ | `bun run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |+ **Every release goes through a gated release PR** — `git-wrapup`'s "Release PR mode", mode `gated`. Three separate runs, never one: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the release digest: theme line, `## Changes`, `## Gates`, changelog link last); `release-pr-review` reviews and fixes on that branch (each fix an ordinary commit on top of the stack, pushed plainly — nothing already pushed is ever rewri

  • AGENTS.md +19 −16

    https://raw.githubusercontent.com/cyanheads/openfda-mcp-server/HEAD/AGENTS.md

    − **Version:** 0.7.9− **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`− **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (via the framework)− | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |− | `ctx.requestInput` | Suspend and ask the caller for more input — `return ctx.requestInput({ inputRequests: { key: inputRequired.elicit({ message, requestedSchema }) } })`. Never returns; the handler is re-entered with the answers. Always present. Replaced `ctx.elicit` in 0.12.0. |− | `ctx.inputs` | Reader over a re-entered request's responses — `.accepted(key, schema)`, `.view(key)`, `.state()`, `.dropped`. Empty on the first round. |− | `ctx.enrich` | Success-path agent context — `.notice()` / `.total()` / `.echo()` / `.truncated()`. Reaches `structuredContent` and `content[]`; lands only where the definition declares an `enrichment` block. |− | `ctx.requestId` | Unique request ID. Passed to the service layer for retry context. |− | `ctx.tenantId` | Tenant ID from JWT or `'default'` for stdio. |− | `ctx.recoveryFor` | Typed resolver returning `{ recovery: { hint } }` for a declared reason; spread into `data` to carry the contract recovery onto the wire. |− **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` on `tool()` to receive a typed `ctx.fail(reason, ...)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the `recovery` field (≥ 5 words, lint-validated) is the single source of truth for the recovery hint. Spread `ctx.recoveryFor('reason')` into `data` to carry the contract re−     throw ctx.fail('recall_endpoint_non_device', undefined, { ...ctx.recoveryFor('recall_endpoint_non_device') });− **Service-layer throws carry `data: { reason }`, and their contract entries carry `thrownBy: 'service'`.** The conformance lint scans handler source only — failures thrown from `openfda-service.ts` aren't visible to it. To make service throws carry the same wire-shape `error.data.reason` clients see from `ctx.fail`, the service passes `data: { reason: 'X' }` to the factory (used in `openfda-service.ts` for `rate_limited`, `upstream_error`, `pagination_limit_reached`, `query_error`, `not_aggregat− - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = package name; `interface.shortDescription` from `package.json` description− - [ ] `.codex-plugin/mcp.json` updated — server name key matches `package.json` name; env vars added for any required API keys− - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; inline `mcpServers` entry with server name key, env vars for any required API keys+ **Version:** 0.8.0+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.11`+ **MCP SDK:** `@modelcontextprotocol/server` ^2.2.0 (via the framework)+ - **Cut noise.** Add only what earns its place: no speculative generality, no guards for states the framework already prevents (Zod-validated params, classified errors), no abstraction until a third caller proves it, no option nothing sets.+ | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any JSON-serializable value; reads return its JSON form (a `Date` comes back as an ISO string). |+ | `ctx.requestInput` | Suspend and ask the caller for more input — `return ctx.requestInput({ inputRequests: { key: inputRequired.elicit({ message, requestedSchema }) } })`. Never returns; the handler is re-entered with the answers. Always present. |+ | `ctx.inputs` | The request's responses — `.accepted(key, schema)`, `.view(key)`, `.state()`, `.dropped` — limited to what the client declared (`elicitation` and its form/url modes, `sampling`, `roots`). Client-supplied: a consent gate trusts only a `ctx.state` record it stored when it asked, bound to the operation, caller, and target (see the `api-context` skill). |+ | `ctx.clientCapabilities` | What the client declared for this request, `undefined` when no view exists. Decides whether to ask for optional context (e.g. roots); never a reason to skip a consent prompt. |+ | `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). |+ | `ctx.requestId` | Request ID — the one every log record of the call carries and its error envelope returns as `data.requestId`. Passed to the service layer for retry context. |+ | `ctx.tenantId` | Tenant ID from JWT; `'default'` for stdio or HTTP with auth off. |+ **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` 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. The framework puts it on the+     throw ctx.fail('recall_endpoint_non_device');+ **Service-layer throws carry `data: { reason }`, and their contract entries carry `thrownBy: 'service'`.** The conformance lint scans handler source only — failures thrown from `openfda-service.ts` aren't visible to it. To make service throws carry the same wire-shape `error.data.reason` clients see from `ctx.fail`, the service passes `data: { reason: 'X' }` to the factory (used in `openfda-service.ts` for `rate_limited`, `upstream_error`, `pagination_limit_reached`, `query_error`, `not_aggregat+       dataframe-drop.tool.ts            # openfda_dataframe_drop (opt-in: OPENFDA_DATAFRAME_DROP_ENABLED)+ **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `framework-skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.+ - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = the unscoped repo name (never the npm scope — `lint:packaging` enforces this); `interface.shortDescription` from `package.json` description+ - [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; every user-supplied variable (API key, contact email, instance URL) is listed in `env_vars` so Codex forwards it from the user's environment. Never write `"KEY": ""` into `env` — an empty value replaces the user's exported key and is read as unset+ - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `author`, `repository`, `license`, `keywords` from `package.json`; inline `mcpServers` entry keyed by the unscoped repo name. Every user-supplied variable is declared under `userConfig` (`type`, `title`, `description`; `sensitive: true` for keys and tokens; `required: true` or `default: ""`) and referenced from `env` as `"KEY": "${user_config.<option>}"` — mirror the `user_config` block in `manifest.json`. Never wri