---
summary: Field-name validation cache + did-you-mean suggestions (fixes #35); new `query` mode in `clinicaltrials_get_field_definitions`; typed error contracts on every tool/resource; framework 0.7.6 → 0.8.13
breaking: false
---

# 2.4.3 — 2026-05-03

The headline fix: invalid `fields` values no longer hang for ~60s through the retry budget before surfacing a generic `serviceUnavailable`. The service now lazy-fetches `/studies/metadata` once, caches a flat field index, and rejects bad names locally in <100ms with did-you-mean suggestions. `clinicaltrials_get_field_definitions` also gained a `query` mode so agents can search the field model by keyword instead of guessing.

## Added

- **`clinicaltrials_get_field_definitions` — `query` mode for keyword search.** Pass `query: "enrollment"` (or "sponsor", "adverse events", etc.) to get a ranked list of matching PascalCase field names with their data types and dot-notation paths. Existing `path` mode and no-input overview still work; `query` and `path` are mutually exclusive. The tool description now leads with "Call this FIRST when you need to know which field names to use…" to encourage discovery use before authoring `fields`/`advancedFilter`/`sort` parameters. Resolves [#35](https://github.com/cyanheads/clinicaltrialsgov-mcp-server/issues/35) (field-name guess flow).
- **`src/services/clinical-trials/field-search.ts`** — Lightweight scorer + Levenshtein fallback over a flattened `FieldIndexEntry[]`. Powers both the new `query` mode (`searchFields`) and the validator's did-you-mean fallback (`nearestPieces`). Custom token-level scoring (substring/prefix/CamelCase tokenization with light stemming) — short identifiers don't benefit from BM25's term-frequency normalization.
- **Pre-validation cache for `searchStudies` and `getFieldValues`.** New `validateFieldsLocally` service option (default `true`) lazy-builds the index from `/studies/metadata` on first call, then rejects unknown `fields` locally with structured `validationError` carrying the offending names and per-name `suggestions`. Fail-open: if the metadata fetch fails (network/timeout), validation is skipped with a warning log and the request falls through to upstream so the agent gets the same error path it would have without pre-validation.
- **Typed error contracts on every tool and resource.** `clinicaltrials_search_studies`, `_get_study_record`, `_get_study_results`, `_get_field_values`, `_get_field_definitions`, `_get_study_count`, `_find_eligible`, and the `clinicaltrials://{nctId}` resource now declare `errors: [{ reason, code, when, recovery, retryable? }]` for `study_not_found` / `ids_not_found` / `field_invalid` / `path_not_found` / `rate_limited` as applicable. Recovery hints centralized in `src/mcp-server/tools/utils/recovery-hints.ts` so the same failure reason produces the same wire payload `data.recovery.hint` (mirrored into `content[0].text` by the framework) regardless of throw site.

## Changed

- **Tool descriptions point at `clinicaltrials_get_field_definitions` for discovery.** `search-studies` (`fields`, `advancedFilter`, `sort`), `get-field-values` (`fields`), and `get-study-count` (`advancedFilter`) descriptions now name the discovery tool with concrete query examples ("enrollment", "sponsor"). The `search-studies.fields` description was trimmed from ~10 inline examples to 5 + a discovery pointer to keep the LLM-facing context lean.
- **Service throws use `httpErrorFromResponse` for unmapped statuses.** Replaces the previous generic `throw new Error(\`HTTP ${status}\`)` so retryable / non-retryable / authn / authz statuses get framework-classified codes instead of a bare 500. Mapped 400/404 paths still emit the existing `validationError` / `notFound` with the recovery contract.

## Fixed

- **Invalid `fields` values no longer eat the full retry budget.** Previously, names like `ConditionList` or `InterventionList` triggered an upstream HTTP 500, which the retry path treats as transient and burns through ~6 retries (~60s) before surfacing `serviceUnavailable`. The new pre-validation cache rejects these locally in <100ms with `validationError` and a did-you-mean suggestion (e.g., `'ConditionList' — did you mean 'Condition', 'Conditions', 'ConditionMeshTerm'?`). Resolves [#35](https://github.com/cyanheads/clinicaltrialsgov-mcp-server/issues/35).

## Dependencies

- **`@cyanheads/mcp-ts-core` `^0.7.6` → `^0.8.13`.** Adopts new `Any{Tool,Resource,Prompt}Definition` types on barrel exports, `httpErrorFromResponse` from `@cyanheads/mcp-ts-core/utils`, and current error-contract shape (`recovery` as required descriptive metadata, `ctx.recoveryFor()` for wire-payload hints).
- **Skills + scripts resynced from framework** — see `skills/` and `scripts/` directories. New `api-canvas` skill copied through. `package.json` `dev` / `dev:stdio` / `dev:http` scripts removed (framework template no longer ships them).
- Bumped devDeps: `@biomejs/biome` `^2.4.13` → `^2.4.14`, `@cloudflare/workers-types` `^4.20260426.1` → `^4.20260503.1`, `tsc-alias` `^1.8.16` → `^1.8.17`.

## Tests

- **+16 tests, 276 → 292 passing.** New coverage in `tests/services/clinical-trials/clinical-trials-service.test.ts`: `field validation (validateFieldsLocally=true)` block (rejection, suggestions, pass-through, getFieldValues path, fail-open, cache reuse, wire-shape) and `searchFieldDefinitions` block (ranking, multi-match, limit, no-match). Existing tests opt out of pre-validation via `validateFieldsLocally: false` in `fastOptions` to keep route-aware mocking simple.
