Skip to content

Releases: txn2/mcp-data-platform

mcp-data-platform-v1.67.3

26 May 07:01
6bab1a3

Choose a tag to compare

What's fixed

Persona editor's Connections tab now shows real connection names

The persona editor was listing toolkits instead of connections. An apigateway instance configured with multiple upstream connections appeared as a single entry (e.g. api) in the Connections tab, so connection-specific allow patterns matched nothing in the live preview and the counter showed 0 allowed even though runtime authorization worked correctly.

Root cause: pkg/admin/system.go:listConnections iterated ToolkitRegistry.All() and emitted one entry per toolkit, ignoring toolkit.ConnectionLister. The parallel handler pkg/admin/connection_handler.go:collectLiveConnections (used by the admin Connections page) already expanded multi-connection toolkits correctly; the /connections endpoint used by the persona editor did not.

Fix: when a toolkit implements toolkit.ConnectionLister, emit one connectionInfo per element of ListConnections() with Name and Connection set to the connection name. Single-connection toolkits are unchanged.

Why runtime authorization was correct anyway

pkg/persona/filter.go:IsConnectionAllowed is invoked at tool-call time with the connection name resolved by the toolkit at that moment, so a persona with allow_connections: [salesforce] correctly authorized calls all along. The bug was purely UI-visible: the editor's "0 allowed" counter, the per-item resolution trace, and the wildcard preview were all computed against the wrong set of items, leaving users unable to tell whether their patterns were correct without testing them in production.

Wire-shape change

GET /api/v1/admin/connections response for a registry with a multi-connection toolkit:

Before (one entry per toolkit):

{"connections": [
  {"kind": "api", "name": "apigateway", "connection": "apigateway",
   "tools": ["api_invoke_endpoint", ...], "hidden_tools": []}
]}

After (one entry per real connection):

{"connections": [
  {"kind": "api", "name": "salesforce", "connection": "salesforce",
   "tools": ["api_invoke_endpoint", ...], "hidden_tools": []},
  {"kind": "api", "name": "stripe", "connection": "stripe",
   "tools": ["api_invoke_endpoint", ...], "hidden_tools": []},
  ...
]}

The connectionInfo JSON shape itself is unchanged (same field names, same types). Only the set of entries returned changes. Single-connection toolkits emit exactly the same payload as before.

Edge case

A multi-connection toolkit whose ListConnections() returns an empty slice (apigateway instance registered but with no upstream connections configured yet) now contributes zero entries to the response, where before it contributed one toolkit-level placeholder. This matches the existing behavior of collectLiveConnections used by the admin Connections page, and reflects reality: there is nothing to authorize against until at least one real connection exists.

Upgrade impact

No breaking changes.

  • Existing allow_connections and deny_connections patterns in persona YAML keep working because the runtime authorization path was already correct.
  • The connectionInfo JSON shape is unchanged (same fields, same types).
  • TypeScript ConnectionInfo types in the UI already declared the per-connection shape; the runtime now matches what the types already claimed.
  • After upgrading, refresh the persona editor: each apigateway / multi-catalog connection now appears as its own row under its toolkit's group, and existing allow / deny patterns will resolve correctly in the live preview without any persona changes.

Changelog

Bug Fixes

  • 6bab1a3: fix(admin): expand multi-connection toolkits in /connections so persona editor shows real connection names (#483) (@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.67.3

Verification

All release artifacts are signed with Cosign. Verify with:

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

mcp-data-platform-v1.67.2

26 May 05:49
d3d9724

Choose a tag to compare

What's fixed

Persona editor blanked on the Connections tab

Switching to the Connections scope in the persona editor crashed the page with Cannot read properties of null (reading 'length'). The gateway toolkit returns a nil []string from Tools() when it has no live upstream connections; the admin /connections endpoint serialized that nil as "tools": null, and the UI dereferenced c.tools.length directly on the wire value.

The fix has two layers: the persona editor now defends with c.tools?.length ?? 0 so a contract violation cannot crash the page, and the backend connectionInfo type now has a MarshalJSON that normalizes Tools and HiddenTools to [] at serialization time. The wire contract is now enforced on the type itself, so any future handler that builds a connectionInfo literal cannot regress the invariant.

Same crash class fixed in adjacent persona endpoints

Reviewing the connections fix surfaced the identical pattern in listPersonas and getPersona:

  • A YAML persona without a roles: key produced "roles": null. RolesPage iterates p.roles and PersonasPanel reads p.roles.length, both without guards, so the persona list page would crash for any such persona.
  • getPersona shipped personaDetail.{Roles, AllowTools, DenyTools, Tools} with the same shape: declared []string without omitempty, no normalization between the in-memory persona and the response.

Both response types now have a MarshalJSON that normalizes the required-non-null arrays. AllowConnections / DenyConnections retain omitempty and stay absent rather than null.

Audit page dropdowns hardened against stale caches

AuditLogPage had filters?.users.map(...) and filters?.tools.map(...), where the optional chain only guarded filters, not the inner array. The backend already normalizes these today, so this was not a live crash, but a stale react-query cache from a pre-fix server would crash the page. Both calls now use filters?.users?.map / filters?.tools?.map, matching the sibling toolkit_kinds and sources accessors.

Responsive persona editor layout

The persona editor now stacks the identity rail and the permissions area into a single flowing column below the lg breakpoint, with the parent panel handling page scroll. The three-pane bounded layout (identity / center explorer / trace) activates at lg+ widths.

Files changed in this release

File Change
pkg/admin/system.go connectionInfo.MarshalJSON normalizes Tools and HiddenTools
pkg/admin/system_test.go Regression test asserting "tools":[] and "hidden_tools":[] on the wire when Tools() returns nil
pkg/admin/persona_handler.go personaSummary.MarshalJSON and personaDetail.MarshalJSON normalize the required-non-null arrays
pkg/admin/persona_handler_test.go Two new subtests covering listPersonas (nil Roles) and getPersona (nil Roles / AllowTools / DenyTools / Tools)
ui/src/pages/audit/AuditLogPage.tsx Defensive guards on filters?.users?.map / filters?.tools?.map
ui/src/pages/settings/PersonaEditor.tsx Defensive c.tools?.length ?? 0 in the connections items memo; responsive stacking below lg

Upgrade impact

No breaking changes. The wire contract for /api/v1/admin/connections, /api/v1/admin/personas, and /api/v1/admin/personas/{name} is unchanged for clients that already tolerated empty arrays; the only behavior change is that fields previously emitted as null for the nil case now emit []. The TypeScript type definitions in the UI already declared these fields as string[], so this brings the runtime in line with the documented contract.

Changelog

Bug Fixes

  • d3d9724: fix(admin): unblock persona editor connections tab and lock null-array wire invariant (#482) (@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.67.2

Verification

All release artifacts are signed with Cosign. Verify with:

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

mcp-data-platform-v1.67.1

25 May 22:58
443fae7

Choose a tag to compare

Highlights

  • Closes #479: apigateway embed-job doom loop. Large OpenAPI specs on CPU-only embedders no longer get stuck in an invisible retry loop. The embed worker now heartbeats its lease, persists each chunk as it completes, and the portal distinguishes a retry from a fresh queue.
  • UI: connection editor lands on the saved connection (#469), the connection-side companion to the catalog landing fix in #458.

What changed

apigateway: heartbeat, per-chunk persistence, configurable embed-job knobs (#480)

The embed-job worker previously held a fixed 10-minute lease, computed every chunk into memory, then persisted the whole spec in one final atomic write at the end. On CPU-only embedders, a ~150-operation spec ran about 10m30s per attempt, hit the reaper's lease ceiling at the last chunk, threw away every prior chunk's work, and retried from scratch. The portal showed queued through every retry with no surface for the failure.

This release closes that path with three coordinated changes:

1. Heartbeat keeps the lease alive while Compute progresses.
A goroutine renews lease_expires_at every lease_duration / 3 while the embed pass is running. A slow batch on CPU Ollama no longer looks abandoned to the reaper at the 10-minute mark. The earlier worker-side context ceiling that defeated this (canceling Compute at lease_duration + 30s regardless of lease state) has been removed; a 1-hour processSafetyBound exists only as a wall-clock backstop against a Compute that hangs without forward progress.

2. Per-chunk persistence preserves progress across retries.
A new additive UpsertOperationEmbeddingsBatch (INSERT ... ON CONFLICT DO UPDATE) writes each chunk's vectors to api_catalog_operation_embeddings immediately. A job that fails on chunk N leaves chunks 0..N-1 visible to the next attempt's dedup pass, which skips the upstream re-embed for those operations. The final atomic UpsertOperationEmbeddings at job completion still runs as the canonical full replacement so operations removed from the spec are cleaned up.

3. Operator-visible retry state.
A pending job with attempts > 0 now renders in the catalog status badge as retrying (N tries) with the upstream error in the tooltip, instead of indistinguishable queued. A doom-looping job is visible at a glance.

New configuration knobs

Under apigateway.embed_jobs:

apigateway:
  embed_jobs:
    workers: 1            # goroutines per pod sharing the queue
    embed_timeout: 5m     # per-batch HTTP call timeout
    lease_duration: 10m   # claim window the heartbeat re-stamps at lease_duration/3
    batch_size: 32        # texts per upstream EmbedBatch call
Field Default Effect
embed_timeout 5m Per-batch HTTP timeout against the embedder.
lease_duration 10m DB lease window; heartbeat re-stamps at lease_duration / 3.
batch_size 32 Texts per upstream EmbedBatch call.
workers 1 Goroutines per pod. CPU embedders saturate at 1; GPU embedders benefit from 2-4.

Startup logs a warning when embed_timeout >= lease_duration.

Operator notes for CPU-only embedders

The defaults are tuned for a GPU embedder. CPU-only Ollama on large specs (~120+ operations) should raise both timing knobs, e.g.:

apigateway:
  embed_jobs:
    embed_timeout: 15m
    lease_duration: 20m

No database migrations. Existing configurations work as-is.

UI: connection editor lands on the saved connection (#469)

After saving a new connection, the panel previously snapped to the first listed connection instead of the one just saved. ConnectionEditor.onSave now passes (kind, name) so the panel sets selectedKey to the freshly-saved connection, and the auto-correct effect gates on !isFetching so the new selection survives the post-save refetch window. Same class of bug as the catalog landing fix in #458.

Dependencies

  • github.com/pgvector/pgvector-go 0.3.0 → 0.4.0 (#471)
  • golangci/golangci-lint-action 9.2.0 → 9.2.1 (#470)

Upgrade

  • No database migrations.
  • Existing configmaps continue to work; the new apigateway.embed_jobs.batch_size and lease_duration fields default to the prior hardcoded values (32 and 10m).
  • Recommended for CPU-only embedders processing large specs: raise embed_timeout and lease_duration per the operator notes above.

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.67.1

Verification

All release artifacts are signed with Cosign. Verify with:

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

Full changelog

  • fix(apigateway): heartbeat, per-chunk persistence, configurable embed-job knobs (#479, #480) — @cjimti
  • fix(ui): land on saved connection instead of first listed (#469) — @cjimti
  • deps: bump github.com/pgvector/pgvector-go from 0.3.0 to 0.4.0 (#471) — @dependabot
  • ci: bump golangci/golangci-lint-action from 9.2.0 to 9.2.1 (#470) — @dependabot

Full diff: v1.67.0...v1.67.1

mcp-data-platform-v1.67.0

24 May 00:32
811fd40

Choose a tag to compare

v1.67.0

Eight PRs spanning a new outbound-auth mode, a security fix on stateless HTTP, audit improvements, smarter outbound content negotiation, an off-by-default cross-toolkit enrichment fallback, two admin-portal redesigns, and the connections-editor cleanup that fell out of building mTLS.

Highlights

  • mTLS for the API gateway, plus a per-connection CA bundle for upstreams behind a private root.
  • Security fix: a cross-user provenance leak on stateless HTTP transport is closed.
  • Audit logs gain caller-class tagging (mcp / rest / admin) and automatic monthly partition rotation.
  • api_invoke_endpoint content-type is now driven by the connection's OpenAPI catalog when present.
  • Cross-toolkit enrichment gets an opt-in semantic-similarity fallback when URN equality misses.
  • Personas page is rebuilt around a single always-on editor with tabbed Permissions / AI behavior.
  • Connections page: cleaner sidebar, validated identifier input, help-modal-driven auth and TLS reference, no more confusing connection_name override slot.

Security

Cross-user provenance leak on stateless HTTP transport (#451)

extractSessionID returned the literal "stdio" sentinel whenever the SDK could not surface a session ID. On HTTP transport every stateless caller (no Mcp-Session-Id header, no initialize handshake) inherited that sentinel and pooled into one shared "stdio" bucket used by ProvenanceTracker, the session gate, the dedup cache, and the workflow tracker.

ProvenanceTracker.Record already guards against empty session IDs to prevent cross-user mixing, but the "stdio" sentinel bypassed that guard. The result: a harvest tool (save_artifact, trino_export, api_export) called by any stateless HTTP caller could return provenance the pool had accumulated regardless of who invoked it.

This release leaves SessionID empty for stateless HTTP callers, which makes the existing empty-skip guard fire and prevents any bucket from accumulating. Stdio still uses the sentinel (one logical client per process). The fix is pinned by TestProvenanceNoCrossUserMixing_StatelessHTTP, which wires two distinct principals through the assembled middleware chain and asserts that Harvest("stdio") and Harvest("") both return zero entries.

Operator impact: for HTTP-transport deployments, pc.SessionID for stateless callers changes from "stdio" to "". Audit handles empty SessionID (column default ''). Memory and knowledge stores are scoped by persona / email and never bucket on session ID alone. The session gate, dedup cache, and workflow tracker still write to an empty-key bucket for stateless HTTP, which is a known cosmetic regression but carries no data leakage.

The PR also reduces resources/list log noise and adds user_id / persona attribution to the surviving DEBUG line.

New: mTLS auth and private-CA trust (#468, closes #466)

Adds standard RFC 5246 / 8446 client certificate authentication to the HTTP API gateway toolkit alongside the existing header-based auth modes, plus a per-connection CA bundle for verifying upstreams behind a private root.

Why this matters

mTLS is the IETF standard for HTTPS client authentication. Common upstreams that need it and that the toolkit could not reach until this release:

  • Service mesh peering (Istio, Linkerd, Consul Connect) where workload identity is a mesh-issued client cert.
  • PKI-fronted enterprise APIs that pre-date OAuth.
  • Healthcare integration engines (Mirth Connect, Rhapsody, InterSystems IRIS HealthShare).
  • Financial messaging: SWIFT REST surfaces, Open Banking / FAPI profiles, bank-direct payment APIs.
  • FedRAMP / DoD-boundary endpoints.
  • HashiCorp Vault when the configured auth method is cert/.
  • Kubernetes API server and etcd direct REST access.
  • Apache Kafka REST Proxy, Schema Registry, NiFi, and similar Apache projects when deployed with the standard security profile.
  • Any HTTPS service signed by a private CA (the CA-bundle half is useful on its own for bearer-over-private-CA cases).

Two orthogonal TLS concerns on every kind: api connection

Field Type Encrypted Purpose
mtls_client_cert_pem PEM no Client certificate chain (leaf first).
mtls_client_key_pem PEM yes Private key matching the cert.
tls_ca_bundle_pem PEM bundle no Extra CAs trusted when verifying the upstream's TLS cert. Appended to the system root pool, never substitutes.

Both groups are optional. An internal HTTPS service behind a private CA may only need the bundle; an upstream that wants client auth but has a public TLS cert needs only cert + key; an upstream that wants both sets all three. With auth_mode: mtls, the cert IS the credential; with any other mode the cert layers on top (bearer + mTLS, oauth2 + mTLS, etc.).

There is no insecure_skip_verify toggle. Self-signed endpoints require pasting their CA into tls_ca_bundle_pem.

Write-time validation

  • Cert and key are mutually required: set both, or set neither.
  • The key must match the cert (signature check via tls.X509KeyPair).
  • Key strength: RSA at least 2048 bits, ECDSA P-256 / P-384 / P-521, or Ed25519. Weaker keys rejected.
  • The CA bundle must contain at least one parseable CERTIFICATE block when set.
  • auth_mode: mtls requires both cert and key.

OAuth modes inherit the CA bundle

connoauth.Config gains a CABundlePEM field threaded through the token-exchange and refresh paths. IdPs behind a private CA work end-to-end for both oauth2_client_credentials and oauth2_authorization_code, on both the initial Connect (code exchange) and the silent refresh path. Client mTLS material is intentionally NOT presented to the IdP (separate security domain).

Cert expiry surfaced (not enforced)

GET responses on /api/v1/admin/connection-instances/api/{name} include mtls_cert_not_after (RFC3339 UTC) computed from the leaf cert. The portal renders a color-coded badge (green at 30 or more days remaining, amber under 30, red on expired). The field is server-derived: PUT bodies are stripped of it and GET strips any stale persisted value before recomputing, so removing a cert cannot leave a phantom expiry behind. The toolkit does not refuse to make calls with an expired cert (the upstream's TLS layer will).

Connections editor cleanup that landed with mTLS

  • connection_name operator-facing override removed. It was always 1:1 with the instance name and surfaced as a confusing second name slot. Existing DB rows that carry the legacy key are tolerated (value silently dropped). TestParseConfig_IgnoresLegacyConnectionNameOverride pins the contract change.
  • ENCRYPTION_KEY caveats dropped from help text. The platform's at-rest encryption is operator-controlled; admins filling out connection forms on a SaaS deployment cannot see or set that variable and don't need to be educated about it. Help text now reads simply "Encrypted at rest. Use [REDACTED] when re-saving."
  • Auth-mode help modal replaces the wall-of-text paragraph under the dropdown with a per-mode table: human-readable label as primary text, machine identifier as small mono subtitle, what the gateway sends, when to use it.
  • TLS / mTLS help modal replaces the inline blurb with the full setup guidance (cert + key, CA bundle, validation rules, expiry-badge semantics). The three PEM textareas and the expiry badge stay in the form itself.

Audit improvements (#452)

Caller-class tagging via source

Tools on the platform are reachable through three entry points, all of which fire the same MCP audit middleware. Until this release the audit row's source was hardcoded to mcp for all three. Now:

source Caller Where set
mcp Real MCP transport (stdio or HTTP/SSE), used by agents Default in pkg/middleware/mcp.go
rest Gateway REST shim at POST /api/v1/gateway/{connection}/invoke, used by NiFi, cronjobs, integrations pkg/gatewayhttp/handler.go::connectInternalSession
admin Admin REST API at POST /api/v1/admin/tools/call, used by portal-driven tool runs pkg/admin/tools.go::connectInternalSession

Operators can now separate agent traffic from external automation in audit_logs without inferring it from user IDs.

Filter + UI surface

  • New query params on /admin/audit/events and /admin/audit/stats: source, toolkit_kind.
  • New dropdown values on /admin/audit/events/filters.
  • New Source column and dropdown on the portal Audit Log page; tooltips on hover; CSV export includes source.

Automatic monthly partition rotation

audit_logs is declared PARTITION BY RANGE (created_date) but until now only had a _default partition, so partitioning provided no benefit and retention DELETEs would get progressively slower at high call rates.

The audit cleanup goroutine now performs three-step maintenance on each tick:

  1. Ensure upcoming audit_logs_YYYY_MM partitions exist (CREATE TABLE IF NOT EXISTS ... PARTITION OF audit_logs FOR VALUES FROM (...) TO (...), starting at next month to avoid partition-key conflicts on brownfield deployments).
  2. Cleanup DELETE for the retention window.
  3. Drop fully-expired named partitions (constant-time storage reclamation as high-volume callers scale).

Step failures are logged and isolated; a transient partition error never blocks the retention DELETE.

Catalog-driven Content-Type negotiation (#454, fixes #453)

api_invoke_endpoint previously chose the outbound Content-Type purely from the runtime type of the body argument: objects and arrays became application/json, strings became text/plain. When a tool-call layer pre-serialized a structured argument before delivery, the body arrived at the gateway as a string and went out as text/plain; charset=UTF-8. JSON-strict upstreams responded 400 even though the catalog clearly declared `requestBody.content.app...

Read more

mcp-data-platform-v1.66.0

20 May 08:12
fc38bb7

Choose a tag to compare

This release ships two changes: observability and shutdown hardening so freshly-deployed pods are immediately profileable and drain cleanly on SIGTERM, and an OpenAPI 3.1 resolver fix so api_get_endpoint_schema no longer drops oneOf / anyOf / allOf branches, response headers blocks, or const values.

Highlights

Metrics enabled by default (#449)

OTEL_METRICS_ENABLED now defaults to true. A freshly-deployed pod exposes Prometheus metrics on :9090/metrics without an env flip. The metrics listener is on a dedicated port isolated from the MCP and admin paths; restrict access with a NetworkPolicy. Set OTEL_METRICS_ENABLED=false to opt out.

Per-tool latency, apigateway upstream latency, and tool-call counters are now scrapable out of the box during sizing exercises and incidents.

Idempotent meter-provider shutdown (#449)

The OTel SDK MeterProvider.Shutdown returns reader is shutdown on the second call. With the new default surfacing a real provider in tests, Platform.Close invoked twice would error. Metrics.Shutdown is now guarded by sync.Once with a cached error, and the call goes through an injectable shutdownFn so the error path is testable without forcing the SDK to misbehave.

Graceful shutdown wiring (#449)

cmd/mcp-data-platform/main.go was calling platform.Close() but never platform.Stop(). Every lifecycle.OnStop callback registered by the embed-jobs subsystem (worker, reaper, reconciler, LISTEN/NOTIFY listener) was being abandoned mid-flight on SIGTERM, leaving worker goroutines running while the DB pool closed underneath them.

Fixes:

  • closeServer now calls platform.Stop(ctx) with a 10s bounded context before platform.Close().
  • The embed-jobs OnStop closure is extracted into Platform.stopAPIGatewayEmbedJobs and runs each Stop call inside a new boundedStop helper that races the work against ctx.Done. A hung component cannot stall past the bounded deadline. Abandoned jobs are safe because their PostgreSQL leases expire and another replica reclaims them.

api_get_endpoint_schema surfaces full OpenAPI 3.1 contract (#450)

The schema flattener walked only properties and items, silently dropping three patterns that are canonical in OpenAPI 3.1:

  • oneOf: [$ref, {type: "null"}] (the 3.1 replacement for the 3.0 nullable: true keyword) collapsed to an empty object. Both the inlined $ref and the null branch disappeared. Same for anyOf and allOf.
  • Response headers blocks were stripped. Callers had no signal about Location on a 301, Retry-After on a 429, ETag / Last-Modified on cache-aware endpoints, or custom headers.
  • const values on properties (common as JSON:API discriminator fields, e.g. type: "show") were dropped, leaving an unconstrained string. enum with a single value followed the same code path.

Fixes:

  • ResponseDetail gains a Headers map[string]HeaderDetail field, marshaled as headers with omitempty.
  • New flattenResponseHeaders helper emits per-header description, required flag, and schema.
  • populateSchemaCompounds now recurses into oneOf / anyOf / allOf / not via a new flattenSchemaRefs helper. $ref branches get inlined by the existing schemaToValue recursion; type: "null" branches are preserved. The same maxSchemaDepth cap that protects against recursive schemas still applies.
  • populateSchemaScalars now copies s.Const alongside the existing scalar keywords.

Five new tests against a JSON:API style OpenAPI 3.1 fixture exercise every reported pattern, including nested cases (anyOf inside array items).

Operator impact

Existing installs

A rolling restart will start exposing /metrics on :9090. If you do not want that, set OTEL_METRICS_ENABLED=false in the deployment env. If you do want it but the port is not yet allowed in your NetworkPolicy, scrapers will fail until you allow it. The endpoint is unauthenticated by design (isolated listener); restrict with a NetworkPolicy.

Recommended manifest tuning

The existing terminationGracePeriodSeconds: 30 is tight for the new shutdown chain. The default budget is roughly 2s pre-delay + 25s HTTP grace + 10s lifecycle stop + close overhead. Suggested:

spec:
  template:
    spec:
      terminationGracePeriodSeconds: 60

For deployments expecting higher traffic, see docs/reference/tuning-and-scaling.md for GOMEMLIMIT / GOMAXPROCS / GOGC guidance. Without them the Go runtime is not cgroup-aware: GOMAXPROCS defaults to the node's CPU count (often 32-64) inside a 500m cgroup quota, causing scheduler thrash; an unset GOMEMLIMIT means the GC will not pace against the memory limit.

Compatibility

  • Tool inputs, tool names, configuration schema, migrations, and wire-format identifiers are unchanged.
  • api_get_endpoint_schema output is additive: the new headers field and the oneOf / anyOf / allOf / not / const keys appear only for specs that declare them. Clients that ignore unknown JSON fields are unaffected.
  • The metrics-default flip is the only behavior change at deploy time. Pin OTEL_METRICS_ENABLED=false in your env if you need the previous posture.

New documentation

  • docs/reference/tuning-and-scaling.md walks through resource sizing by traffic tier, Go runtime env vars, the HA-safety matrix for multi-replica deployments, PostgreSQL pool sizing per replica count, a four-stage graceful-shutdown budget tied to terminationGracePeriodSeconds, and an HPA example.

Changelog

Features

  • 6440705: feat(observability): enable metrics by default and harden shutdown (#449) (@cjimti)

Bug Fixes

  • fc38bb7: fix(apigateway): surface oneOf/anyOf/allOf, response headers, and const in api_get_endpoint_schema (#450) (@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.66.0

Verification

All release artifacts are signed with Cosign. Verify with:

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

mcp-data-platform-v1.65.0

20 May 03:14
23f2087

Choose a tag to compare

Highlights

Adds HTTP Basic auth (RFC 7617) to the api-gateway toolkit. Operators can now onboard the long tail of REST APIs that never moved off Basic (Jenkins, on-prem Jira / Confluence Server / DC, internal apps) without standing up a sidecar to translate Basic to Bearer.

What changed

auth_mode: basic joins the existing none, bearer, api_key, oauth2_client_credentials, and oauth2_authorization_code modes. Outbound requests get Authorization: Basic base64(username:password) per RFC 7617. The password config key was already on the platform's encryption-at-rest sensitive-keys list, so encryption and admin-API redaction work without additional changes.

Validation enforces RFC 7617 §2 (no : in the userid) and rejects CR/LF/NUL in either field as header-smuggling vectors. Smuggling defenses run before the RFC check, so a payload that contains both (e.g. alice\r\nX-Injected: 1) surfaces the security-relevant error first. Empty password is permitted to support the legacy token-in-userid pattern some APIs use.

Operator-visible changes

New connection config

config:
  base_url: "https://jenkins.example.com"
  auth_mode: "basic"
  username: "svc-account"
  password: "the-api-token"
Field Required Notes
auth_mode: basic yes Selects the new mode.
username yes The userid. Stored in cleartext at rest because RFC 7617 §2 sends it in clear after base64 decoding regardless. Rejected if it contains :, CR, LF, or NUL.
password no The password. Encrypted at rest via the platform FieldEncryptor. Admin API redacts to [REDACTED]. May be empty for the legacy token-in-userid pattern. Rejected if it contains CR, LF, or NUL.

Admin portal

The API connection editor in the admin portal now offers Basic (RFC 7617) on the auth-mode dropdown, with username and (sensitive) password fields.

Behavior preserved

  • No wire-format changes.
  • No database migrations.
  • No public API changes.
  • Existing connections (none, bearer, api_key, OAuth) are untouched.

Upgrade notes

  • No operator action required beyond rolling the pod. Existing connections continue to work; the new mode is purely additive.
  • To use Basic auth on a new connection, select Basic in the portal or PUT /api/v1/admin/connection-instances/api/<name> with auth_mode: basic and username / password set.

Detailed changes

  • #447 / #448. New AuthModeBasic constant and basicAuth authenticator. New Username / Password config fields. validateBasicAuth enforces RFC 7617 plus CR/LF/NUL smuggling defenses (in that order so the security error wins when both apply). NewAuthenticator dispatch and the invalid-mode error message updated. UI: new dropdown option plus username/password form fields in ConnectionsPanel.tsx. Docs updated across README.md, docs/server/api-gateway.md, docs/llms.txt, and docs/llms-full.txt. Patch coverage 100% on the 37 changed executable lines.

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.65.0

Verification

All release artifacts are signed with Cosign. Verify with:

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

Full changelog

v1.64.1...v1.65.0

mcp-data-platform-v1.64.1

19 May 23:45
c91e711

Choose a tag to compare

Highlights

Hotfix for the api-gateway embed-jobs worker timing out on every spec write against CPU-only Ollama. Spec saves on v1.64.0 with a 26+ operation spec ended in the badge cycling between indexing and failed indefinitely; this release stops that loop and adds the regression test that would have caught it before v1.64.0.

What changed

v1.64.0 introduced the batched /api/embed endpoint for embedding requests but kept the 30-second HTTP timeout that was tuned for the older singular /api/embeddings path. A batched POST of 26+ texts on CPU-only Ollama routinely exceeds 30 seconds, so the worker timed out on every attempt, retried five times, failed terminally, then the reconciler re-enqueued the job because operation count and embedding count still disagreed. The cycle never converged.

The fix is scoped to the asynchronous worker. Request-path embedding callers (memory_recall, memory_manage, capture_insight, apigateway queryVectorFor) continue to use the 30s default because their singular calls return in 1-3 seconds on CPU Ollama and operators do not want a wedged Ollama to hold an MCP tools/call open for minutes. Only the embed-jobs worker gets the longer timeout, scoped via a new config knob.

Operator-visible changes

New configuration

apigateway:
  embed_jobs:
    workers: 1
    embed_timeout: 5m
Field Default Affects
embed_jobs.embed_timeout 5m The api-gateway embed-jobs worker's batched /api/embed POSTs against Ollama. Request-path embedding calls remain governed by memory.embedding.ollama.timeout (default 30s).

GPU-backed Ollama deployments can tighten this value to keep the worker's failure floor short; CPU-only deployments should leave it at the default.

Behavior preserved

  • No wire-format changes.
  • No database migrations.
  • No public API changes.
  • No config breakage: deployments that did not set the new key get the documented 5m default automatically.

Upgrade notes

  • No operator action required beyond rolling the pod. The new default behaves correctly for both CPU and GPU Ollama deployments. Operators who already worked around the bug on v1.64.0 by setting memory.embedding.ollama.timeout: 5m can leave that override in place or remove it; the worker no longer depends on it.
  • Tightening the worker timeout on GPU embedders is now possible. Operators on fast embedders who want a short failure floor on the worker (e.g. 30 seconds) can set apigateway.embed_jobs.embed_timeout: 30s without affecting other consumers.

Detailed changes

  • #445 / #446. Scope the long batch timeout to the embed-jobs worker. embedding.DefaultTimeout stays at 30s. New apigateway.embed_jobs.embed_timeout config (default 5m). New Platform.workerEmbedder() helper constructs a dedicated Ollama provider with that timeout; the shared p.embeddingProv used by request-path callers is unchanged. Three new unit tests cover the scoping including a regression-prevention assertion that the shared instance keeps its 30s default when the worker timeout is configured.

    New internal/testollama helper and pkg/platform/integration_embedjobs_realollama_test.go (build tag integration) drive the production embedding path end-to-end against a real Ollama container running nomic-embed-text. Pre-v1.64.1 behavior would fail this test on the same batch shape that caused the incident; post-fix it passes with margin. This pattern is reusable by upcoming embedding consumers (DataHub semantic search, prompt discovery, knowledge insights recall, portal asset search) so synthetic-delay stubs are no longer the only coverage available.

Workaround on v1.64.0 (no upgrade required)

Add timeout: 5m under memory.embedding.ollama in the platform configmap and roll the deployment.

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.64.1

Verification

All release artifacts are signed with Cosign. Verify with:

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

Full changelog

v1.64.0...v1.64.1

mcp-data-platform-v1.64.0

19 May 20:51
ffed470

Choose a tag to compare

Highlights

  • API gateway embed-job queue is observable and parallelizable. A long-running spec embed no longer sits silent at 0/N indexed for minutes. The worker publishes a chunk-progress counter on every batch boundary so the catalog badge ticks upward live, and a new config knob lets multiple worker goroutines drain the queue in parallel.
  • Ollama batch embedding is now actually batched. EmbedBatch calls Ollama's POST /api/embed (singular path, plural input field) instead of N sequential POST /api/embeddings. Modern servers return one HTTP round-trip per batch; older servers are detected on the first 404 and the path transparently falls back. A 164-operation spec on CPU-only Ollama drops from ~10 minutes to model-inference floor.
  • Embedding provider misconfiguration is visible and fail-safe. When memory.embedding.provider is unset the platform substitutes the noop placeholder and now: logs one actionable WARN at startup, refuses to wire the apigateway embed-job queue (no more [0,0,...,0] rows in api_catalog_operation_embeddings masquerading as indexed), persists memory writes with Embedding: nil, exposes the state via GET /api/v1/admin/embedding/status, and surfaces an amber banner in the API Catalogs and Memory panels.
  • SRE-grade observability at the chokepoints. Prometheus metrics on every tools/call (latency P50/P95/P99, error rate, in-flight counts) and on every outbound api_invoke_endpoint (HTTP status class, transport-error category, per-connection latency). Cardinality-bounded by design: a closed allow-list of attributes, user-identifying fields kept off labels.
  • Correct gateway semantics for api_invoke_endpoint. A 4xx/5xx response from the upstream is no longer reported as a gateway failure. Wire-level HTTP from the REST shim stays 200 with the upstream code embedded in the payload; only true transport failures and timeouts surface as 502 / 504.
  • Auth chain output is quieter and easier to correlate. Successful fallthroughs between OIDC, API-key, and admin-key auth no longer log routine fallthroughs as warnings. Every auth decision carries a request-correlation ID so multi-step flows can be reconstructed from a single grep.

Operator-visible changes

New configuration

apigateway:
  embed_jobs:
    workers: 1   # default; raise to 2 or 4 if you have many specs and a fast embedder

CPU-only embedders typically saturate at 1 because the bottleneck is the model. Increase only when you have evidence the worker is the bottleneck.

New admin endpoints

Method Path Purpose
GET /api/v1/admin/embedding/status Returns {kind, model, dimension, status}. status="unconfigured" indicates the noop placeholder is in use; the portal renders an amber banner in this state and the apigateway embed-job queue refuses to start.
GET /metrics Prometheus scrape endpoint. Histograms + counters on tool-call latency, outbound apigateway latency by HTTP status class, and audit-write rate.

Wire-format additions (backward compatible, omitempty)

GET /api/v1/admin/api-catalogs/{id}/embedding-status response gains:

{
  "embedded_so_far": 12
}

embedded_so_far is the worker's in-flight chunk-progress counter. While job_status == "running" the portal renders this against operation_count so a long embed pass shows incremental progress instead of staying at 0/N until the final atomic upsert commits embedding_count.

Database migration

000046_api_catalog_embedding_jobs_progress adds embedded_so_far INTEGER NOT NULL DEFAULT 0 to api_catalog_embedding_jobs. On PostgreSQL 16 this is metadata-only for INTEGER columns; no table rewrite, no extended lock. Existing deployments roll forward automatically on platform start.

UI

  • API Catalogs panel: per-spec badge ticks indexing N/M upward during a running embed, distinct from queued (amber, 0/M) and N/N indexed (green).
  • API Catalogs + Memory tab on My Knowledge: amber banner when memory.embedding.provider is unset, naming the config key and clarifying that semantic features are disabled while lexical / keyword / entity-URN lookup still works.

Detailed changes

Features

  • #428 / #431. Prometheus observability at platform chokepoints. Histograms + counters on the MCPToolCallMiddleware and on api_invoke_endpoint outbound HTTP. Two instrumentation sites cover every toolkit at once: every tool call goes through the middleware, every model-driven API call goes through the gateway. Cardinality allowlist: tool, connection, persona, status_category, http_status_class, toolkit_kind. Forbidden labels: anything user-identifying (request IDs go on trace spans, not Prometheus labels). Pure Prometheus exporter; no OTel agent required.
  • #430 / #437. Incremental embed progress + configurable worker concurrency. Worker publishes embedded_so_far at every chunk boundary via a new Store.UpdateProgress call; the column lives in api_catalog_embedding_jobs and is read alongside the existing job-status fields. New apigateway.embed_jobs.workers config (default 1) spawns N goroutines that share the queue; existing FOR UPDATE SKIP LOCKED + lease guarantee keeps them from racing on the same job. After each successful Claim the worker notifies a sibling so a backlog drains in parallel. Worker exposes Concurrency() for assertion in wiring tests.

Bug fixes

  • #429 / #436. Silent noop embedding provider stored [0,0,...,0] vectors. Five defenses now refuse to persist zero vectors: apigateway wiring skips the queue when the embedder is noop; memory handleRemember and handleUpdate persist Embedding: nil instead of zero vectors; knowledge capture_insight skips the memory-store update under noop; Provider interface gains Kind() string + a package-level IsConfigured(p) bool helper for one-line guards; startup logs one WARN naming the config key. Existing recall-side guard at memory/recall.go:127 already refused all-zero query embeddings; the write side now mirrors that.
  • #435. Ollama EmbedBatch was named "batch" but didn't batch. Sent N sequential POST /api/embeddings calls (one per text). Now sends one POST /api/embed per batch with the full input array. Detects servers that lack the batch endpoint (HTTP 404) on the first call, logs one WARN naming the URL and model, transparently falls back to the singular path. Probe is paid at most once per provider lifetime. Eliminates 31 of every 32 HTTP round-trips on modern Ollama for the default batch size of 32.
  • #432 / #433. api_invoke_endpoint conflated upstream and transport failures. A 4xx/5xx response from the upstream was wrapped as a tool error (IsError=true) and the REST shim translated it to a 502, hiding the real upstream status. Now the wrapping CallToolResult reports IsError=false for any upstream response with a status code; Status carries the upstream HTTP code; the REST shim returns 200 with the upstream code embedded in the body. Only true transport failures (DNS, dial, TLS, mid-stream EOF) and request-timeouts surface as IsError=true and map to wire 502 / 504 respectively. Outcome classification (OutcomeOK, OutcomeUpstream4xx, OutcomeUpstream5xx, OutcomeTransportErr, OutcomeUpstreamTimeout) flows through to audit logs via _meta.audit_outcome so operators can grep by category.
  • #434. Auth chain fallthrough noise and missing request correlation. OIDC followed by API-key followed by admin-key authentication logged every successful fallthrough as a WARN; an admin-key request produced two warnings for every call. Successful fallthroughs are now silent (Debug level); only auth failures or unexpected errors log at WARN. Every auth decision carries a request-correlation ID propagated from the inbound request through every chain step, so a multi-step auth flow can be reconstructed by grepping a single ID.

Upgrade notes

  • Migration is automatic. migrate.Run on startup applies 000046. No operator action required.
  • No breaking config changes. apigateway.embed_jobs.workers is new and defaults to 1, which preserves prior single-goroutine behavior.
  • No breaking wire-format changes. New JSON fields are omitempty; consumers that ignore unknown fields are unaffected.
  • The Kind() string addition to the public embedding.Provider interface is a source-incompatible change for external implementations. Both in-tree implementations (Ollama, noop) are updated. Anyone vendoring this module and implementing embedding.Provider externally needs to add Kind() string returning a stable identifier.
  • First scrape of /metrics exposes zero values. Prometheus alerting rules that fire on absent series should be reviewed; on a freshly-deployed v1.64.0 some histograms only appear after the first relevant call.

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.64.0

Verification

All release artifacts are signed with Cosign. Verify with:

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

Full changelog

v1.63.0...v1.64.0

mcp-data-platform-v1.63.0

18 May 16:43
10e336c

Choose a tag to compare

Highlights

REST gateway shim for non-MCP HTTP clients (#427)

api_invoke_endpoint is now reachable over plain HTTP at:

POST /api/v1/gateway/{connection}/invoke

so callers that cannot speak MCP/JSON-RPC (Apache NiFi's InvokeHTTP, Airflow's HttpOperator, curl, internal pipelines) can drive the same kind: api connections that MCP callers use, without learning the MCP tools/call envelope.

Why this matters. Before this release the only non-MCP path to the API gateway was POST /api/v1/admin/tools/call. That endpoint is admin-only, double-wraps the response in an MCP content envelope, and forces external callers to know tool-name conventions. NiFi, Airflow, and similar HTTP pipelines could not reasonably integrate against it. The result: an enterprise with a fully-audited, persona-gated, OAuth-fronted API gateway still had to hand out raw vendor credentials to its data movement layer. This release closes that gap with a first-class REST surface.

No forked auth path

Every REST request is routed through an in-memory MCP session against the platform's already-assembled *mcp.Server. The existing MCPToolCallMiddleware chain (authenticator + persona authorizer + route policy + audit logger) governs REST callers identically to MCP callers. No auth, audit, or policy logic is reimplemented. If a persona is denied api_invoke_endpoint over MCP, the same persona is denied over REST. Audit events for REST calls appear in the same audit_logs table with the same field set as MCP calls.

Auth

The same headers other REST surfaces on the platform accept:

  • Authorization: Bearer <token>
  • X-API-Key: <key>

Both resolve through the platform's authenticator chain (OIDC bearers, OAuth 2.1 access tokens, configured API keys).

Request shape

{
  "method":          "GET",
  "path":            "/v1/things",
  "query_params":    { "limit": 50 },
  "headers":         { "X-Trace": "abc" },
  "body":            null,
  "timeout_seconds": 30
}

The connection is taken from the URL path and authoritatively overrides any connection field in the body.

Response shape

{
  "status":      200,
  "headers":     { "Content-Type": ["application/json"] },
  "body":        { "items": [ ... ] },
  "duration_ms": 245
}

Status-code contract

The platform's HTTP status is platform-level only. The upstream's HTTP status is surfaced inside the response body as InvokeOutput.status:

Platform status Meaning
200 The platform performed the upstream call. Read body.status for the upstream's response code.
400 Malformed request body (missing method/path, invalid JSON).
401 Missing or invalid credential.
403 Persona or route-policy denied the call.
404 Unregistered connection.
500 Internal failure.

This split keeps "the platform refused" distinguishable from "the upstream returned 4xx/5xx" for response-code routing in NiFi-like systems. A NiFi pipeline can route on the platform status and still inspect body.status inside the body for the upstream outcome.

The route is mounted only when at least one kind: api toolkit instance is loaded.

Apache NiFi recipe

Wire an InvokeHTTP processor to the gateway:

Property Value
HTTP Method POST
URL https://<platform-host>/api/v1/gateway/{connection}/invoke
Content-Type application/json

Set an X-API-Key (or Authorization) attribute on the FlowFile and reference it from an InvokeHTTP dynamic property mapped to the header name. The FlowFile content is the JSON body above; downstream processors can use EvaluateJsonPath to lift $.status and $.body into attributes for the response-code routing relationships.

Other clients

  • Airflow HttpOperator (or SimpleHttpOperator): set endpoint to the gateway URL, data to the request JSON, and pass the credential through extra_options.
  • curl:
    curl -X POST \
      -H "X-API-Key: $PLATFORM_KEY" \
      -H "Content-Type: application/json" \
      -d '{"method":"GET","path":"/v1/things"}' \
      https://platform.example.com/api/v1/gateway/vendor/invoke
  • Generic HTTP clients in any language: the route accepts the JSON body shape above and returns the JSON response shape above. No MCP/JSON-RPC handshake.

What this release does NOT change

  • api_invoke_endpoint behavior over MCP: identical.
  • /api/v1/admin/tools/call behavior: identical.
  • Connection storage, OAuth refresh, static_headers, catalogs, route policy: all unchanged.
  • No new migrations, no new config keys, no new auth modes.
  • No breaking changes.

Upgrade notes

Drop-in upgrade from v1.62.x. No config edits required. The new route auto-mounts when an apigateway toolkit instance is present.

Other changes

  • ci: bump github/codeql-action from 4.35.4 to 4.35.5 (#426)

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.63.0

Verification

All release artifacts are signed with Cosign. Verify with:

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

Full changelog

v1.62.4...v1.63.0

mcp-data-platform-v1.62.4

18 May 03:32
3c3f8c4

Choose a tag to compare

Try It tab can now invoke platform-level tools

The portal Tools "Try It" tab's connection field rendered as a disabled, empty dropdown for any tool that takes connection as a parameter rather than being toolkit-bound. Affected tools: api_list_endpoints, api_get_endpoint_schema, api_invoke_endpoint, api_export, and similar platform-level meta-tools. Operators had no way to invoke these from the portal.

This release fixes the picker and adds an empty-state helper for the no-matching-connections-registered case.

PR #425.

Root cause

ToolForm.tsx rendered the connection field with a single branch that assumed every tool was bound to a connection at toolkit-registration time:

// pre-fix
<select disabled value={selectedConnection}>
  <option value={selectedConnection}>{selectedConnection}</option>
</select>

That works for tools the toolkit registered under a specific connection. It fails for platform-level meta-tools whose schema has connection: "" and needs the operator to pick a target at call time. The select rendered empty with no options.

Fix

ToolForm branches on the binding state

State Render
selectedConnection non-empty (bound) Locked select showing the bound name. Unchanged behavior.
selectedConnection empty + connections of the tool's kind exist Enabled <select name="connection" required> populated from a new availableConnections prop. Operator picks at call time.
selectedConnection empty + no matching connections Disabled select plus an amber helper message: "No {kind} connections registered. Add one in Settings to invoke this tool."

A new prop availableConnections?: EffectiveConnection[] is filtered by the caller to the tool's kind so the dropdown lists only valid targets.

TryItTab wires the picker

  • Fetches connections via useEffectiveConnections().
  • Filters by schema.kind (an api tool sees api-kind connections only; an mcp tool sees mcp-kind only).
  • Passes the result through as availableConnections.

Replay/History invariant preserved

The handler destructures connection off form params into a fresh outParams object rather than mutating in place:

const { connection: _routing, ...rest } = params;
outParams = rest;

This is load-bearing for the History panel and the Replay action. The params reference passed in is the same object stored in the history entry one line earlier (addHistoryEntry({ ..., parameters: params })). A naive delete params.connection would strip it from the already-stored entry, breaking Replay (which re-applies entry.parameters to the form). Destructuring keeps the history reference intact and removes connection only from the wire payload.

The bound case continues to take precedence so connection-grouped tools still route to their toolkit.

Tests

ui/src/pages/tools/ToolForm.test.tsx (new), 5 tests pinning every branch:

  • Locked select renders when selectedConnection is bound.
  • Enabled picker with name="connection" and the supplied options renders when unbound.
  • Empty-list edge case renders the amber helper plus a disabled select.
  • The operator's pick propagates into params.connection on submit.
  • Required-empty guard blocks the no-pick submit case.

Connection names in the tests use salesforce and github (generic vendor names per the standing rule).

Adversarial pre-commit review

Round 1 surfaced 2 substantive findings, both fixed:

  1. The original draft used delete params.connection, which mutated the history entry's stored reference and broke Replay for unbound tools. Switched to destructuring into a new outParams object.
  2. The new test file used vendor-specific connection names. Scrubbed to generic ones.

Round 2: CLEAN.

make verify green (full suite: Go test/race, coverage, patch coverage, golangci-lint, gosec, govulncheck, semgrep, codeql, dead-code, mutation testing, GoReleaser dry-run, UI typecheck and vitest).

Upgrade notes

  • No schema change, no config change, no breaking API change. Drop-in upgrade from v1.62.3.
  • Platform-level tools that were previously uninvokable from the portal are now usable: api_list_endpoints, api_get_endpoint_schema, api_invoke_endpoint, api_export, and any future tool of the same shape (registered without a bound connection, takes connection as a parameter).
  • Connection-bound tools render unchanged.
  • When no connections of the matching kind exist, the field surfaces an amber helper line instead of a silently empty dropdown.

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.62.4

Verification

All release artifacts are signed with Cosign. Verify with:

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