Fire Arrow Server 2.1.0
Fire Arrow Server 2.1.0 has been released.
- (security) When the MCP interface is enabled, its endpoints now apply each caller's own authorization rules, and the MCP CDS bridge now enforces an access check (MCP remains off by default)
- (feature) Faster CarePlan Task materialization when the server reaches its database over a network connection
- (bugfix) Attachment reads on historical resource versions no longer break after cleanup or garbage collection
- (bugfix) An invalid CarePlan Task status in configuration now stops the server at startup instead of failing later
MCP Endpoints Enforce Caller Authorization
The Model Context Protocol (MCP) interface is disabled by default and has been since 2.0.0. It is turned on only when a deployment explicitly sets spring.ai.mcp.server.enabled=true to expose the upstream HAPI MCP surface. Deployments running the default configuration are not affected by anything described here.
When MCP was enabled, its endpoints (/mcp/** and /sse) did not resolve the caller's identity, so FHIR tool calls were authorized under the Public rule set rather than the caller's own rules. Because authorization is deny-by-default (nothing is permitted unless a rule explicitly allows it), this was the restrictive direction. An authenticated caller was downgraded to Public and could reach less than intended, not more. No caller could read or write data that the Public rules did not already allow, so there was no unauthorized-access exposure on the MCP FHIR surface.
Starting with 2.1.0, the MCP endpoints resolve the caller's identity the same way the FHIR API does and run every tool call through the same authorization checks. A call is now evaluated against the caller's own rules (the same result it would get on the FHIR API) and still falls back to the Public rules when no identity is presented. MCP is now usable with real per-caller permissions instead of restricting every caller to Public.
The MCP CDS bridge is also brought under authorization. Previously it had no access check of its own; it now performs a caller-scoped Patient read check before responding and forwards the caller's token into CDS prefetch. If that check cannot be completed, the request is denied rather than answered.
No action is required. Deployments that leave MCP off are unaffected. Deployments that enable MCP, deliberately or by accident, now have MCP traffic evaluated under the same rules as the rest of the API automatically, with no additional configuration.
Faster CarePlan Task Materialization
2.0.0 substantially reduced CarePlan Task materialization time, measured against a database on a local loopback connection. Deployments whose server reaches PostgreSQL over a network, where each database round trip carries real latency, did not see the full benefit, because materializing a CarePlan still issued one forced-id write per Task, and each write cost a separate round trip.
2.1.0 writes all of a CarePlan's Tasks in a single atomic transaction bundle, so the per-Task round trips collapse into one exchange with the database. The improvement scales with the latency between the server and its database and is largest for CarePlans that produce many Tasks.
Measured against a production-realistic PostgreSQL workload (~101 Tasks per CarePlan) with a 3 ms database round-trip latency:
| Scenario | Before | After | Improvement |
|---|---|---|---|
| Materialization write path (per Task) | 30.4 ms | 14.1 ms | 2.2× |
| Synchronous subscribe, end to end | 5.8 s | 2.6 s | 2.3× |
On a loopback connection with no network latency, materialization time is unchanged from 2.0.0. The gain appears once database round trips have a cost.
The largest share of the end-to-end improvement above comes from how quickly a subscription becomes active on the synchronous CarePlan/$subscribe-due-events and $renew-due-events paths. By default, HAPI FHIR queues subscription activation and drains it on a five-second poll, which a synchronous caller waits through. Deployments that use CarePlan events should set hapi.fhir.subscription.immediately_queued: true to activate subscriptions immediately and remove that wait. This setting is not enabled in the default configuration, so existing deployments are unaffected unless they opt in.
No mandatory configuration changes are required. A background reconcile sweep (fire-arrow.careplan-events.cleanup.reconcile-sweep-interval, default PT5M) runs on by default as a safety net that repairs any CarePlan whose materialization did not complete, so the Task set becomes consistent without operator intervention.
Bug Fixes
Attachment reads on historical versions could break after cleanup
Reading a historical version of a resource (_history/N) whose attachment had since been cleaned up could return a dead attachment URL instead of the stored content.
When a resource with an attachment was soft-deleted or updated, attachment cleanup and the orphan-blob garbage collector treated only the current version as live and removed the underlying blob, even though a retained historical version still referenced it. A later read of that historical version then found the blob gone and left an unusable firearrow:// URL in the response.
Both paths are now history-aware: a blob is removed only once no retained version (current or historical) still references it. This is controlled by fire-arrow.binary-storage.retain-with-history (default true); setting it to false restores the previous behavior. As part of this change, the orphan-blob garbage collector is now enabled by default in the Azure production image. Its first run is a dry run that reports the blobs it would reclaim without deleting anything, so operators can review one cycle before setting BINARY_GC_DRY_RUN=false to allow deletion. Partitioned deployments should confirm one dry-run cycle before enabling deletion.
Invalid Task status configuration now fails at startup
A typo in the CarePlan Task status settings (initial-status, due-status, or auto-transition-terminal-state under fire-arrow.careplan-events.task.*) let the server start normally and then fail only later, from inside a running materialization or transition job, aborting in-flight scheduling work for a misconfiguration that could have been caught at boot.
These values are now validated when the server starts. An unrecognized status stops startup with a clear error rather than surfacing during the first scheduling run. In addition, auto-transition-terminal-state must name a terminal status (cancelled, failed, or entered-in-error); a non-terminal value is now rejected at startup as well. Confirm these settings are valid Task statuses before upgrading. Correctly configured deployments are unaffected.