---
summary: "Fix format() drop-offs across 4 tools; fall back to per-ID calls when batch rejects on a single bad NCT"
breaking: false
---

# 2.3.3 — 2026-04-21

### Fixed

- **`analyze_trial_landscape` prompt — `focusAreas` argument unusable** — The arg was declared as `z.array(z.string())`, but per the MCP spec, `prompts/get` sends `arguments` as `Record<string, string>` with no wire format for arrays. Sending an array failed protocol-layer validation; sending a comma-string failed the tool-layer schema. Changed `focusAreas` to a comma-separated `z.string()`; `generate()` splits on `,`, trims, and filters. Verified via HTTP: `"phases, sponsors"` now produces `Focus the analysis on: **phases, sponsors**`. Closes #27.
- **`clinicaltrials_get_study_record` — `format()` still dropped `hasResults`, `resultsSection`, `derivedSection`, `documentSection`** — Follow-up to #18. Added four render blocks: `**Has Results:**` with a pointer to `clinicaltrials_get_study_results` when true; `**MeSH Conditions:**` and `**MeSH Interventions:**` from `derivedSection.*BrowseModule` (prefers `browseLeaves`, falls back to `meshes`); a `## Results Summary` stub with counts for outcome measures, adverse events, participant-flow periods, and baseline measures; a `## Documents (N)` section with kind tags (Protocol/SAP/ICF) and upload dates from `documentSection.largeDocumentModule.largeDocs`. Extended `RawStudyShape` with `documentSection` and `browseLeaves`. Verified live on NCT03722472 (renders all four) and NCT02194738 (confirms `MeSH Interventions` path with 16 drug/procedure terms). Closes #28.
- **`clinicaltrials_get_field_values` — `format()` silently dropped up to 235 values** — Hard `.slice(0, 15)` on `topValues` with no tail message. `structuredContent` carried up to 250 values, but `content[]` consumers lost everything beyond the 15-value cap. Added an explicit tail: `… and N more values in structuredContent (of X unique; topValues capped at 250)`. Documented the 250 cap in the output schema's `.describe()`. Verified on `LeadSponsorName` (49,996 unique values → 15 rendered + tail message). Closes #30.
- **`clinicaltrials_get_field_definitions` — `format()` dropped field `description`** — The field tree's `description` is the one place in the server's tool surface where authoritative data-model documentation lives, but `format()` never read the property, so LLM-only clients lost the context. Added a `  <description>` line beneath each field when present, for both top-level and nested renders. Verified on `protocolSection.identificationModule` — `nctId` now renders its canonical description in `content[]`. Closes #31.
- **`clinicaltrials_get_study_results` — partial-invalid batch cascaded failure to valid NCTs** — Follow-up to #23. The batch endpoint (`filter.ids`) rejects the whole request if any single ID is malformed or nonexistent, and the handler mapped that batch-wide error to every input ID — so a single bad ID dropped all valid results. Changed the catch block to fall back to sequential per-ID `getStudy` calls (honors the service's ~1 req/sec rate limit); only truly failing IDs now land in `fetchErrors`. Added `erroredIds` Set to prevent the downstream missing-ID loop from double-counting fallback failures. Verified on `[NCT03722472, NCT05956821, NCT00000000]`: valid IDs succeed, only `NCT00000000` errors. Closes #32.

### Changed

- **`RawStudyShape`** — Extended `derivedSection.conditionBrowseModule` and `interventionBrowseModule` with `browseLeaves: Array<{ id?, name?, relevance? }>`; added top-level `documentSection.largeDocumentModule.largeDocs: Array<{ filename?, hasIcf?, hasProtocol?, hasSap?, label?, typeAbbrev?, uploadDate? }>`. All additive — no breaking changes.
