Skip to content

mcp-data-platform-v1.60.1

Choose a tag to compare

@github-actions github-actions released this 13 May 08:01
· 127 commits to main since this release
4ca8e18

What's new in v1.60.1

This release unifies the OAuth 2.1 authorization_code flow for all outbound connection kinds (MCP gateway, HTTP API gateway, and any future kinds) onto one shared implementation, and adds a real Keycloak IdP to the dev environment so the full flow is testable end-to-end.

Why this matters

The MCP gateway and the HTTP API gateway each had their own OAuth implementation — separate HTTP handlers, separate token tables, separate refresh loops, separate frontend hooks, and separate status components (~95% duplicated). That divergence produced three user-visible symptoms:

  1. API connection "Connect" button surfaced a generic error on unsaved forms. Save-first was required by both backends, but only the API view showed it as a failure — because the API view had no OAuth status block to surface state.
  2. API connection view didn't show OAuth status at all. The status endpoint and the status component were built only for MCP.
  3. MCP refresh tokens appeared to vanish across replicas. The MCP toolkit's in-memory oauthTokenSource cached loaded=true after the first read, so a Connect that landed on pod A was invisible to pod B until restart.

All three traced to one architectural cause: the same OAuth flow had been forked instead of shared. This release fixes that.

Backend changes

  • New pkg/connoauth/ package — single Source / Store / Exchange flow keyed on (kind, name). Refresh uses golang.org/x/oauth2 and the package explicitly persists the rotated token (including any new refresh token) after every successful exchange, so refresh-token rotations survive process restarts and multi-replica reads. That is the bug-3 fix.
  • Migration 000039connection_oauth_tokens table backfilled from the prior gateway_oauth_tokens and apigateway_oauth_tokens tables. The old tables are retained for one release for emergency rollback; a follow-up migration drops them.
  • Unified admin handler (pkg/admin/connection_oauth_handler.go) — single /api/v1/admin/connections/{kind}/{name}/oauth-{start,status,reacquire} route set, kind-dispatched through an OAuthKindHandlers registry. The canonical callback URL /api/v1/admin/oauth/callback is unchanged from the prior MCP path, so customer IdP configurations (Keycloak / Auth0 / Okta / Microsoft App Registrations) do not need to be reconfigured. The legacy /api/v1/admin/api-gateway/oauth/callback URL is registered as an alias so existing API-gateway IdP configurations continue to resolve.
  • Per-kind OAuthKindHandler implementations in both toolkit packages, plus connoauth-backed TokenStore adapters so the toolkits' existing Authenticators read and write the unified table.
  • Reacquire endpoint returns the post-refresh OAuthStatus body, so the portal status card updates immediately instead of waiting for the next 10-second poll.

Frontend changes

  • Shared ConnectionOAuthStatusCard rendered for every connection kind with an identical surface (Connect, Reconnect, Refresh now, status grid, error band). Replaces the MCP-only inline card. Keyed on (kind, name) so the card fully remounts on connection switch — no more stale "Token refreshed" banners bleeding from one connection to another.
  • Single unified hook set: useStartConnectionOAuth(kind), useConnectionOAuthStatus(kind, name), useReacquireConnectionOAuth(). The legacy MCP/API helpers now delegate to these.
  • Connection selection mirrors into the URL as ?kind=...&name=... via history.replaceState, so the OAuth callback's returnURL restores the same connection the operator was viewing. Default selection follows the alphabetical kind order shown in the left sidebar instead of whatever order the backend returned first.
  • Removed the useless "Show sensitive" toggle on the connection view — it only flipped between two opaque masks (********[REDACTED]), so it served no purpose.
  • LoginForm rebuilt with logo + portal title + tagline matching the rest of the brand surface. New portal.tagline field flows through /api/v1/admin/public/branding so deployments can override the strapline alongside title and logo.

Dev environment

  • Real Keycloak container in make dev, backed by the existing Postgres (auxiliary keycloak database created idempotently in start.sh — works on both fresh volumes and pre-existing volumes that pre-date the keycloak addition).
  • Realm mcp-platform pre-seeded with:
    • Three OAuth clients: mcp-data-platform-portal (operator login), oauth-mcp-dev (MCP fixture connection), oauth-api-dev (API fixture connection).
    • A realm-role mapper so realm roles flow into the ID token (Keycloak's default puts them only on the access token).
    • Two test users: admin@example.com / admin-password (role dp_admin) and analyst@example.com / analyst-password (role dp_analyst).
  • Vite proxy honors X-Forwarded-Host so OAuth start, callback, and logout all stay on the operator's original origin (:5173 vs :8080). Previously the OAuth callback would silently bounce sessions started on the Vite dev server over to the Go server, leaving the operator viewing the embedded (compiled-in) UI bundle instead of the live source.
  • browsersession.LogoutHandler derives post_logout_redirect_uri from the request scheme + host so dev sessions return to their source origin rather than a static configured target.
  • /portal/auth/* added to the Vite proxy so OIDC login flows reach the Go backend instead of falling through to the SPA's 404 (which previously made the login page appear to reload itself when the operator clicked Sign in with OIDC).
  • Persona role definitions accept both unprefixed (API-key auth) and dp_-prefixed (Keycloak OIDC, filtered by role_prefix "dp_") variants so the same persona matches every login path.

Bug fixes shipped

# Symptom Root cause Fix
1 API Connect button generic error API view had no OAuth status block Shared ConnectionOAuthStatusCard rendered for every kind
2 No OAuth status on API view Status endpoint + component were MCP-only Unified /oauth-status route + shared card
3 MCP refresh tokens vanishing across replicas In-memory loaded=true cache in toolkit connoauth.Source reads store on every call; refresh explicitly persists rotated tokens
4 Browser silently jumped to :8080 after OAuth Vite proxy used changeOrigin: true without X-Forwarded-Host xfwd: true on the Vite proxy + dynamic callback-URL derivation in the Go handler
5 Logout returned "Invalid parameter: id_token_hint" Realm-level accessTokenLifespan: 60, so id_tokens expired before the operator clicked Sign out Bumped to 1800s in the realm import
6 "Refresh now" UI threw Unexpected end of JSON input Reacquire endpoint returned empty 200; apiFetch always parses JSON Endpoint now returns the post-refresh OAuthStatus body
7 Action banners bled across connections Inner component reused across kind/name prop changes Card keyed on (kind, name) so it fully remounts
8 Trino auto-selected on first Connections visit Default picked connections[0] (backend order) Default now follows the alphabetical kind order shown in the sidebar
9 OAuth callback dropped operator on a different connection Selection was component state only URL mirroring via history.replaceState + initial-selection restore

Stats

  • 51 files changed, ~6,100 insertions, ~450 deletions
  • 1 new package (pkg/connoauth/)
  • 1 new migration (000039_connection_oauth_tokens)
  • 1 new admin route group
  • 1 new shared React component (ConnectionOAuthStatusCard)
  • Test coverage: total 87%; pkg/connoauth 91.3%, pkg/admin 89.3%, pkg/toolkits/gateway 95.3%, pkg/toolkits/apigateway 93.1%.

Operator upgrade notes

  • No database migration is required by the operator. Migration 000039 applies automatically on startup. Existing tokens in gateway_oauth_tokens and apigateway_oauth_tokens are backfilled into the new unified connection_oauth_tokens table; the old tables remain readable for one release.
  • No IdP reconfiguration is required. The canonical OAuth callback URL /api/v1/admin/oauth/callback is unchanged. The legacy /api/v1/admin/api-gateway/oauth/callback URL continues to resolve as an alias.
  • New optional config field: portal.tagline (string) appears in /api/v1/admin/public/branding as portal_tagline and is rendered on the login screen below the portal title. Defaults to "Sign in to access the platform." when unset.
  • make dev now requires Docker pull of quay.io/keycloak/keycloak:25.0.6 on first start (~500 MB). Subsequent runs reuse the image.

Follow-ups (deferred, not in this release)

  • Drop migration for gateway_oauth_tokens and apigateway_oauth_tokens after one stable release on the unified table.
  • Deletion of the legacy pkg/admin/gateway_oauth_handler.go and pkg/admin/api_gateway_oauth_handler.go files (kept as fallback when ConnOAuthStore is nil; routes are not registered when the unified path is active).
  • Deletion of the /api/v1/admin/api-gateway/oauth/callback legacy alias after one release.

Changelog

  • 4ca8e181 — refactor: unify MCP + API connection OAuth, add Keycloak dev IdP (#393) (@cjimti)

Installation

Homebrew (macOS)

brew install txn2/tap/mcp-data-platform

Claude Code CLI

claude mcp add mcp-data-platform -- mcp-data-platform

Docker

docker pull ghcr.io/txn2/mcp-data-platform:v1.60.1

Verification

All release artifacts are signed with Cosign. Verify with:

cosign verify-blob --bundle mcp-data-platform_1.60.1_linux_amd64.tar.gz.sigstore.json \
  mcp-data-platform_1.60.1_linux_amd64.tar.gz