Fluid Forge Docs Baseline: CLI 0.8.6
Release Date: May 29, 2026 Status: Superseded by 0.8.7 (supersedes 0.8.5)
Headline
0.8.6 ships the MCP output-port gateway and, with it, contract schema fluidVersion 0.7.4. The headline is that agentPolicy stops being declarative metadata and becomes runtime-enforced at the gateway: when an expose carries an expose.mcp block, fluid mcp output-port serve enforces allowedModels / deniedModels, allowedUseCases / deniedUseCases, rate limits, token budgets, and per-tenant row filters on every tools/call. Gateway identity is now cryptographic — JWT bearer + mTLS — and the cloud-IAM compilers for BigQuery row-access policies and AWS Lake Formation are real implementations (no longer stubs), joined by PostgreSQL and AWS Athena drivers, PII/PHI value redaction, and an audit webhook forwarder. The release also fixes two important bugs: a monitoring daemon-thread leak that OOM-hung Python 3.13/3.14 CI, and a ConfigManager shared-defaults pollution bug.
Schema 0.7.4 is backward-compatible with 0.7.3 — existing contracts validate unchanged; the new expose.mcp block is additive and opt-in. (A schema-minimize regression in 0.8.6 made this compatibility incomplete; it was fixed in 0.8.7.)
What changed in v0.8.6
1 — FLUID schema 0.7.4: runtime agentPolicy enforcement at the MCP gateway
Schema 0.7.4 adds the expose.mcp block. Its presence does two things: it declares an expose agent-consumable over MCP (sampling caps + classification), and it opts that expose into the gateway.
When the gateway serves an expose that carries expose.mcp, it loads policy.agentPolicy and enforces it on every tools/call:
- Model gating —
allowedModels/deniedModels. The caller'smodel_idcomes from the MCPclientInfohandshake. - Use-case gating —
allowedUseCases/deniedUseCases, keyed off the caller's declareduseCase.
The gateway is built on Anthropic's official mcp Python SDK (>=1.20,<2.0); the prior custom JSON-RPC dispatcher and stdio transport were removed. See fluid mcp and MCP output-port gateway for the full surface.
CLI overrides for incident response — --allow-models, --deny-models, --allow-use-cases, --deny-use-cases on fluid mcp output-port serve. CLI values replace the contract values entirely (not merged); the audit event records policySource: "cli" vs "contract" so an audit reader can tell ops overrides from declared policy.
2 — Cryptographic gateway identity: JWT bearer + mTLS
Caller identity is no longer self-attested. A new AuthValidator strategy supports:
shared-token— the existing v0.7.4 default (setFLUID_MCP_AUTH_TOKEN; the gateway returns401on any unauthenticated request before the SSE handshake).jwt— RS256 / ES256 / EdDSA validated against an issuer's JWKS endpoint (checksiss/aud/exp/ signature). Configured JWT claims map intocaller_attributes, sopolicy.rowFilters${caller.<attr>}placeholders resolve cryptographically rather than via self-attestation.none— operator opt-out; the gateway warns loudly at startup.
mTLS metadata forwarded by a reverse proxy (X-Client-CN + X-Client-Fingerprint) is mirrored into the identity for combined attribution. Reverse-proxy templates for production (Caddy + nginx, with mTLS + bearer token + SSE buffering) ship in examples/mcp-output-port-docker/proxy/.
3 — Real cloud-IAM compilers (defence-in-depth beyond the gateway)
So that "bypass the gateway and query the warehouse directly" is also gated, the cloud-IAM compiler now emits runnable policy for two more targets (previously -- TODO stubs):
- BigQuery row-access policy —
CREATE OR REPLACE ROW ACCESS POLICY ON <table> GRANT TO (<service_accounts>) FILTER USING (<predicate>), withcaller.user → SESSION_USER()mapping andagentPolicy.allowedModels → serviceAccount:fluid-mcp-<MODEL>@<project>GRANT clauses. - AWS Lake Formation — a runnable boto3 script (
lakeformation.create_data_cells_filter+grant_permissions) bound to per-LLM IAM roles (arn:aws:iam::<ACCOUNT>:role/fluid-mcp-<MODEL>). Paste into CDK / Terraform or run directly.
These join the already-shipping Snowflake row-access-policy and Postgres CREATE POLICY emitters, which honour the same agentPolicy + rowFilters contract.
4 — New engine drivers: PostgreSQL + AWS Athena
- PostgreSQL driver — psycopg v3, read-only sessions enforced at connect time, per-statement timeout via
SET LOCAL statement_timeout, and:p_<idx>→%(p_<idx>)sparameter rewrite (SQL-injection-safe). Exercised end-to-end against the dockerized Postgres inexamples/mcp-output-port-docker/. - AWS Athena driver — boto3 default credential chain (env /
~/.aws/credentials/ IAM role / OIDC),StartQueryExecution→ poll → page-through withExecutionParameters.
EngineDriver.close() is hoisted to the base class so every driver has a uniform, idempotent close path; out-of-tree driver registration via register_driver() is now pinned by tests so a refactor can't silently break private wheels.
5 — Row-level PII / PHI value redaction
Columns marked sensitivity: pii, sensitivity: phi, or sensitivity: sensitive in expose.contract.schema keep their key visible (so the agent knows the field exists and can write COUNT(DISTINCT) aggregates) but their values are replaced with [REDACTED-PII] before the row leaves the gateway. This is distinct from columnRestrictions, which drops the column wholesale.
6 — Audit webhook forwarder for multi-instance HA
Set FLUID_MCP_AUDIT_WEBHOOK_URL (and optionally FLUID_MCP_AUDIT_WEBHOOK_HEADER_AUTH) and every audit event is POST-ed on a daemon thread to a SIEM aggregator (Splunk HEC, Datadog, Elastic, Loki). It is best-effort: webhook failures never block the local-disk write — the local copy remains the source of truth. Pairs with the existing FLUID_AUDIT_ROOT redirect and audit-log rotation (FLUID_AUDIT_MAX_AGE_DAYS, FLUID_AUDIT_MAX_TOTAL_MB).
Bug fixes
- Daemon-thread leak that OOM-hung CI on Python 3.13 / 3.14.
forge.core.monitoring.MonitoringSystemstarted four background daemon workers per instance and only stopped them on an explicitshutdown(). Code that constructed instances and dropped them (notably the test suite) leaked threads without bound; across a long run the per-thread virtual stacks exhausted address space and the OS OOM-killed the process. Workers now stop promptly via athreading.Event, live instances are tracked in aWeakSet,shutdown()/ context-manager support is added, and a per-test fixture drains them. ConfigManagercould corrupt process-wide defaults._load_defaultsshallow-copied the module-levelDEFAULT_CONFIG, sharing its nested dicts — so a laterset("logging.level", …)or config-file merge mutated the shared defaults in place, affecting every otherConfigManagerin the process. It now deep-copies the defaults.- MCP gateway rate limiter no longer leaks a background thread. Replaced PyrateLimiter's
Limiter(which spins a per-instance "leaker" thread) with an in-process monotonic-clock deque sliding window — functionally identical for the single-replica gateway, with no thread and no dependency. Drops thepyrate-limiterdependency.
Notable for upgraders
- Schema
0.7.4is backward-compatible. Existing0.7.3(and older) contracts validate unchanged. Theexpose.mcpblock is additive and opt-in — nothing enforcesagentPolicyat runtime until you add it and serve the expose over the gateway. - The gateway must not be exposed on an untrusted network in
shared-token/nonemode. Use thejwtauth mode (with a JWKS issuer) or front the gateway with the mTLS reverse-proxy templates before exposing it beyond localhost. The CLI prints a loud startup warning when identity is self-attested. agentPolicyis now load-bearing. If you hadagentPolicyblocks as documentation only, addingexpose.mcpand serving via the gateway will start enforcing them — reviewallowedModels/deniedUseCasesbefore you serve.- New env vars.
FLUID_MCP_AUDIT_WEBHOOK_URL,FLUID_MCP_AUDIT_WEBHOOK_HEADER_AUTH, plus the existingFLUID_MCP_AUTH_TOKEN,FLUID_MCP_RATE_LIMIT/FLUID_MCP_RATE_WINDOW_SECONDS,FLUID_MCP_MAX_CONCURRENCY, and the circuit-breaker / token-budget toggles. Seefluid mcp. - New drivers need their extras. The PostgreSQL driver needs
psycopgv3; the Athena driver uses boto3 — install the relevant extra for the engine you serve. - Python 3.13 / 3.14 users: the monitoring thread-leak fix resolves the OOM hang — upgrade to
0.8.6if you saw long test runs or long-lived processes balloon memory.
What changed in the docs
fluid mcp— the MCP output-port gateway command surface:serve, transports, auth modes, the CLI policy-override flags, rate-limit / concurrency / circuit-breaker env vars.- MCP output-port gateway — runtime
agentPolicyenforcement,expose.mcpschema, JWT + mTLS identity, row filters, PII/PHI redaction, cloud-IAM compilers, and the audit forwarder. RELEASE_NOTES_0.8.6.md— this file.
Installing
pip install --upgrade data-product-forge
pip install "data-product-forge==0.8.6"
# Verify
fluid version
# -> 0.8.6
Archive note
Older release notes remain available: 0.8.5, 0.8.4, 0.8.3, 0.8.0, 0.7.11, 0.7.9, 0.7.1.