mcp-data-platform-v1.96.0
v1.96.0 is a security and hardening release: CSRF protection for every cookie-authenticated mutation, OAuth 2.1 fixes with behavior changes operators should read before upgrading, strict configuration parsing that surfaces silent config drift, a fix that makes memory's recall-first dedup actually converge, and the frontend test suites joining CI.
Upgrade notes (read before upgrading)
Three behavior changes from the OAuth hardening (#777) and one from configuration parsing (#779):
- Dynamic Client Registration is now deny-by-default (#750). With
oauth.dcr.enabled: trueand noallowed_redirect_patterns, registration used to accept any redirect URI; it is now denied with a clear error and a startup warning. Configureallowed_redirect_patterns, or set the new explicitoauth.dcr.allow_all_redirect_uris: trueto keep the old behavior. - Access tokens now mint
audas the issuer URL per RFC 9068, with the requesting client in aclient_idclaim (#751). Tokens minted by earlier versions fail validation after upgrade; clients receive a 401 and silently refresh into a valid token. Rolling multi-replica upgrades self-heal the same way once the rollout completes. - Refresh-token rotation is fail-closed (#748). Rotation now atomically consumes the old token and fails the grant if it cannot, instead of discarding the delete error. Storage outages return 500
server_errorso spec-compliant clients keep their refresh token and retry. - Unknown configuration keys now log a prominent WARN at startup (#757). This release is the warn phase: loading continues, so existing configs keep working. Set
config.strict: trueto reject unknown keys with a hard startup error (recommended). A future release flips the default.
Security
CSRF protection for cookie-authenticated mutations (#749, PR #778)
Every POST/PUT/PATCH/DELETE route reachable by browser-session cookie auth (portal, admin, managed resources) now requires a CSRF token. Previously the only cross-site defense was the cookie SameSite mode, which is operator-configurable.
- Stateless token:
HMAC-SHA256over the session subject with the session signing key, domain-separated from the session HMAC, verified with a constant-time compare. No server-side store. - Cookie-only enforcement: API-key and Bearer requests are exempt (browsers do not attach those credentials automatically). Safe methods (
GET,HEAD,OPTIONS,TRACE) always pass. - Recoverable failure: a missing or invalid token on a valid session returns
403, not401, so the SPA shows a recoverable error instead of logging the user out. The SPA obtains the token fromGET /me. - Session-cookie hardening:
auth.browser_session.same_siteis now configurable, with a startup warning when a cross-site mode weakens the cookie defense.
OAuth 2.1 server fixes (PR #777)
Beyond the three behavior changes above: SQL and redirect allowlist tightening, plus the remaining Phase 2 review-findings fixes across the memory store, enrichment, the Trino toolkit, platform lifecycle, and URN handling (#765, #751, #748, #747, #753, #750, #752, #759, #763, #754, #755, #760). URN construction is consolidated in one place (pkg/urnbuild).
Memory
Near-duplicate captures now consolidate (#762, PR #782)
memory_capture promises recall-first behavior: a restatement of something already known supersedes it instead of duplicating. A live deployment showed the promise not holding: two active, nearly identical memories on one entity, captured seconds apart.
Root cause: the recall similarity search could match records that were already superseded. A dead predecessor would absorb the new capture's supersede while the active duplicate survived. Fixed, plus the full set of consolidation behaviors from the issue:
- Recall matches the right records. Superseded rows are excluded; stale rows remain matchable, since a restatement is exactly how a stale record gets corrected.
- A capture supersedes every restatement it finds (cosine >= 0.9), not just the best one, so capturing over an already-duplicated set heals the whole set. The response's
supersededfield keeps its single-id shape (best match); newsuperseded_idslists all of them. - Update-vs-create candidates. Matches in the 0.75 to 0.9 band come back as
similar_existing(id + score) so the agent can consolidate instead of appending a near-duplicate. - New
memory_managecommands:review_duplicateslists the caller's own active high-similarity pairs (pgvector self-join, per-user by construction), andconsolidatesupersedes a chosen duplicate by the record kept, with ownership checks and a guard that the kept record is active. This is the backstop for duplicates created before this release.
Proven against real Postgres + pgvector in integration tests: three rapid restatements converge to a single active record.
Configuration
Strict YAML parsing and a docs round-trip gate (#757, #770, #775, #769, PR #779)
Closes a class of silent configuration drift: documented keys that never took effect and toolkit examples that would load zero toolkits if copied verbatim.
config.strictoption with warn-phase default (see upgrade notes). Detection covers every typed struct, top-level and nested; free-form maps (thetoolkitstree, per-toolkitconfig:maps, persona names) stay exempt by design.- A CI test now extracts every fenced YAML block from
README.mdanddocs/**, strict-loads each config-shaped block, and validates toolkit shape, so documented examples cannot drift from the parser again. - Toolkit YAML examples corrected to the real
enabled/instancesnesting; the shippedconfigs/platform.yamlis verified strict-clean.
CI and verification
- Frontend suites now run in CI (#765, PR #777): the vitest suite (264 tests) and the interactive Playwright e2e suite (43 tests), both MSW-backed and mirrored in
make verifyfor CI/local parity. Eight e2e specs that had drifted against UI changes were repaired. - Docs orphan gate (#772, PR #781): a repo-level test ensures every
docs/**/*.mdpage is reachable from the MkDocs nav, intentionally unlinked, or excluded from the build. Pages in none of those sets were published but unreachable by browsing;mkdocs build --strictdoes not catch this.
Documentation
- README restructured into a curated front door (#774, PR #780): 521 lines down to 229, grouped feature tables with every row linking to its docs page, current portal screenshots throughout (all 112 regenerated, light and dark), and
docs/llms.txtrewritten to spec-length entries. - Config/docs correctness sweep (#768, #769, #767, #770, #772, #773, PR #776): removed the broken
definitions:wrapper from persona examples (30 occurrences across 15 files; it silently produced no working personas), swept ~20 documented keys/flags that do not exist in code, fixed the built-in persona examples to match the fail-closed defaults, merged the orphaned observability page into the canonical one, and regenerated CLAUDE.md's project map from the real tree with a test so it cannot drift.
Changelog
- 9602064: fix(memory): consolidate near-duplicate captures via recall-first supersede (#762) (#782) (@cjimti)
- b42b586: test(docs): gate against orphaned documentation pages (#772) (#781) (@cjimti)
- c5f7147: docs: restructure README into a curated front door and regenerate screenshots (#774) (#780) (@cjimti)
- 1c43a99: fix(config,docs): strict YAML parsing + config/docs drift sweep (#757, #770, #775, #769) (#779) (@cjimti)
- ca7efbf: feat(security): CSRF protection for cookie-authenticated mutations (#749) (#778) (@cjimti)
- df82237: fix(oauth,ci): Phase 2 hardening - frontend CI net, OAuth fixes, allowlists, URN consolidation (#777) (@cjimti)
- a5af84b: docs: fix broken config examples, nav gaps, and stale defaults (#776) (@cjimti)
Full diff: v1.95.0...v1.96.0
Installation
Homebrew (macOS)
brew install txn2/tap/mcp-data-platformClaude Code CLI
claude mcp add mcp-data-platform -- mcp-data-platformDocker
docker pull ghcr.io/txn2/mcp-data-platform:v1.96.0Verification
All release artifacts are signed with Cosign. Verify with:
cosign verify-blob --bundle mcp-data-platform_1.96.0_linux_amd64.tar.gz.sigstore.json \
mcp-data-platform_1.96.0_linux_amd64.tar.gz