Database MCP Migration Contract¶
Design for an opt-in pilot, not current runtime behavior
This document records the accepted opt-in pilot host contract for #1171 and the validation plan for #1172, under migration epic #1170. The released 0.12.1 server still uses the legacy database backend. This document adds no new runtime flags, dependency, or tool schema and does not authorize a cutover.
Decision: one database target per process¶
The library-backed database router binds to one fixed executor for the lifetime of the MCP process. redisctl chooses that target at startup. Database calls cannot switch it through tool arguments, profile edits, or shared mutable state. Separate targets require separate MCP server instances.
Cloud and Enterprise remain redisctl-owned toolsets. Their existing per-call profile selection is unchanged. A combined server can expose those toolsets alongside one database target; a Cloud API profile is not a database target.
Ownership boundary¶
| Responsibility | Owner |
|---|---|
| Database tool names/schemas, command semantics, structured results | redis-mcp |
| Access/effect classification, capability checks, output budgets and command timeouts | redis-mcp |
| Redis protocol, reconnection and Cluster connection setup | redis-tower, through redis-mcp |
| Profile and credential resolution, fixed startup target and connection policy | redisctl |
| Cloud/Enterprise/app toolsets, visibility, policy and combined transport | redisctl |
| Audit integration and process lifecycle | redisctl, using public library lifecycle APIs |
redisctl must not copy library schemas or response normalization into parallel
handlers. Its composition layer binds credentials and maps host policy/audit
behavior onto the library router. It must not recreate the legacy backend as a
production redis-rs executor behind the new schemas.
Startup resolution¶
These rules apply only when the opt-in backend loads database tools. A server without library-backed database tools does not require a database target.
- Resolve CLI/environment values normally: explicit
--database-urloverridesREDIS_URL; explicit--profilevalues overrideREDISCTL_PROFILE. Rust embedders provide the equivalent values through the builder, rather than implicitly reading CLI environment variables. - Validate explicitly selected profile names against the startup configuration. An unknown profile or unreadable/invalid configuration needed for profile resolution is an error, not an empty configuration fallback. Deduplicate repeated names. Count only selected database profiles as target candidates; selected Cloud/Enterprise profiles do not enter this count.
- Apply the following matrix. A present but empty/malformed URL is an error, not a reason to fall back to a profile.
| Direct URL | Selected database profiles | Outcome |
|---|---|---|
| Present | None | Use the direct URL |
| Absent | Exactly one | Resolve that database profile |
| Present | One or more | Reject conflicting target sources |
| Absent | More than one | Reject ambiguous database profiles |
| Absent | None | Reject missing target |
There is no implicit target from default_database, the only configured
database profile, the first profile in a list, or localhost. Explicit selection
keeps the pilot deterministic even when local configuration changes. Multiple
configured but unselected database profiles are not ambiguous. An inherited
REDIS_URL conflicting with a selected database profile must produce a safe
instruction to remove the conflicting source, never echo the URL.
Validate target syntax and resolve required credentials at startup, but connect lazily on the first database operation. Initialization and catalog discovery must not require network access. A first-connect failure remains a bounded, redacted operation error, not an implicit switch to another target.
Connection policy¶
--clusterchooses the executor mode at startup; it is not a per-call field. The first pilot can use a single configured seed. Multiple-seed configuration is separate scope, not a new implicit CLI contract.- Preserve authentication, TLS verification and the selected logical database. Reject a nonzero logical database in Cluster mode instead of ignoring it.
- Never downgrade TLS, retry without credentials, or enable insecure mode to recover from a connection error. Plaintext development connections remain an explicit configuration choice.
- Client naming must eventually apply after authentication/protocol/database
setup and before traffic on every physical connection, including reconnects
and new/replacement Cluster nodes. A one-time
CLIENT SETNAMEdoes not satisfy this contract. Until the upstream replay gate passes, the pilot must document its limitation and must not claim production naming parity. - Profile changes do not retarget a running router. Restart the MCP process to resolve new configuration or credentials. In-process credential refresh is separate work and must preserve fixed target identity.
Credential boundary¶
The migrated tool schemas contain neither url nor profile target fields.
Credentials and connection URLs stay in host configuration and executor state;
they must not enter model-facing schemas, tool results, errors, tracing, audit
records, or instruction/resource text. A safe target summary may identify its
source, profile name and standalone/Cluster mode, but must not print the URL.
Sanitize parse, credential-resolution and connection errors as well as success paths. In particular, do not forward raw TOML source snippets, expanded environment values, or upstream errors containing authentication material. Tests use unique fake secrets to prove this boundary on failure paths.
Multiple-target deployment¶
Use clearly named configurations in the MCP client, each launching its own process. For example, these existing launch options illustrate the fixed-target selection pattern without introducing a future backend-selector flag:
{
"mcpServers": {
"redis-dev": {
"command": "/absolute/path/to/redisctl-mcp",
"args": ["--profile", "dev-db", "--tools", "database,app"]
},
"redis-staging": {
"command": "/absolute/path/to/redisctl-mcp",
"args": ["--profile", "staging-db", "--tools", "database,app"]
}
}
}
Profiles are configured through trusted local credential flows, not by putting secrets in the client launch JSON. In HTTP mode, all sessions sharing a router share its fixed database target. HTTP transport does not supply built-in client authentication; keep loopback binding or use a trusted authenticated TLS gateway.
Pilot scope and compatibility¶
The pilot is an additive compile-time feature plus an explicit runtime backend selection, with final option names settled in its implementation PR. Neither building the feature nor changing a dependency makes it the default.
Only read-only Keyspace/Strings operations are composed initially. No duplicate tool registrations are permitted: the opted-in pilot replaces those database families for that server, rather than merging matching legacy names. Unmigrated database families are absent from the pilot catalog, not silently served by a second database backend. Cloud, Enterprise, app tools, transport and skill behavior remain unchanged. The legacy catalog stays the default and rollback requires only deselecting the pilot backend.
Removing accepted url/profile fields is a breaking catalog change, even
though the fields are optional. New structured outputs and changed return
behavior also require explicit compatibility review. Keep a separate reviewed
pilot catalog; do not replace the legacy fixture to hide differences. At the
declared 1.0 boundary, users of per-call target selection migrate to one startup
target per process. A default cutover before that boundary requires a separate,
explicit compatibility decision, not an incidental dependency update.
Pilot acceptance matrix¶
Each row requires recorded test evidence in the implementation PR. This table is a plan, not a claim that migration tests already exist or pass.
| Area | Required evidence |
|---|---|
| Default path | Default builds/runtime retain the legacy catalog; feature alone does not opt in; selector without compiled support fails clearly |
| Target resolver | CLI/environment precedence, explicit URL, named profile, missing/unknown/wrong-type profile, duplicate name, conflicting sources, multiple selected databases, and no implicit default |
| Offline startup | Initialization and discovery succeed for a valid target with Redis offline; no connection until first database operation |
| Standalone parity | Compare redis_scan, redis_type, redis_get, missing keys, TTL, Unicode and supported byte handling against one shared live fixture |
| TLS/authentication | Verified TLS and correct credentials succeed; wrong credentials, untrusted CA, malformed URL and refused connection fail safely without fallback |
| Cluster | Representative reads across slots and MOVED/topology behavior; Cluster database-index rejection; document library scan/fan-out differences |
| Policy/visibility | Tier, global/per-toolset allow/deny, disabled database toolset and presets are enforced on discovery and direct invocation; explicit allow cannot expose writes outside the read-only pilot |
| Isolation | Concurrent requests/sessions remain on the fixed target; changing a profile cannot move an already-built router |
| Catalog/results | Reviewed name/schema/annotation/output diff; no url/profile fields; no duplicate registration; normalize only test comparisons, not production responses |
| Audit/secrets | Representative success, denial, timeout and parser/auth/TLS failures preserve host auditing without fake secret/URL leakage |
| Budgets/timeouts | Connect and command failure paths terminate within documented limits; large replies respect library output budgets |
| Lifecycle/rollback | Exercise process stop and disposal; record unresolved upstream lifecycle limitations; deselection restores the legacy backend |
| Build/release | fmt, all-target/all-feature Clippy, workspace/integration tests, minimal-feature builds, strict rustdoc and clean-consumer checks against the pinned Git revision; registry packaging before crates.io publication; binary release packaging remains separate |
Development checkpoint and dependency gates¶
The target contract was accepted for the opt-in pilot by merging #1175 on 2026-10-05. The isolated pilot #1176 implements it for validation. That acceptance does not approve a release or a default compatibility change. The later public-source decision is separate.
The library's
published redis-tower dependency update #124
is merged. The pilot pins reviewed library revision
8eff8240679d38157ee08b9d5e5eaeb656ce7550
(#126) for
reproducible committed builds. Local path overrides are allowed for coordinated
development, but must not appear as machine-specific dependencies in committed
manifests or lockfiles.
Skills from #1174 are now embedded in main. Their resources and prompts remain
host-owned; the pilot's redisctl://skills index must describe the actual
composed, policy-visible catalog. Missing library families must not be advertised
as available merely because a bundled workflow refers to them.
Public source validation¶
The repository owner made the pinned library public on October 6, 2026. The former internal-source fetch blocker is resolved. Anonymous Git checkout and uncached Cargo resolution for default and pilot features have been verified; see the pilot guide and #1176 for current-head evidence. No dependency-access credentials are required. Source resolution still matters in every affected Cargo entry point, not only compile jobs:
ci.yml: Clippy/core checks, unit/integration/live tests, platform builds and coverage.docs.yml: default embedding API and all-feature workspace rustdoc.cargo-deny.yml: both dependency-graph check groups.release.yml: dist plan metadata, local/global builds and any host metadata resolution;release-plz.yml: version/package/publish resolution.
The lockfile-based cargo audit in security.yml is distinct from these graph
checks. Cargo still resolves optional Git dependencies when their feature is
disabled. A warm cache is not clean-consumer evidence, and public source
availability is not compilation/test evidence. Release build, host and publish
steps remain separately gated; do not run them merely to validate the pilot.
Publication is not a pilot implementation gate. Before publishing dependent redisctl crates to crates.io, switch to an actual registry dependency and verify registry packaging and clean-consumer resolution. Binary release packaging and supported distribution remain separate decisions, not pilot prerequisites. The library server has its own library-first release ordering; its binary is not a redisctl dependency.
Before making the new backend default:
- Complete the pilot, each family's parity/catalog gates, and compatibility migration treatment; delete duplicate handlers only in reviewed slices.
- Adopt and verify the library's connection setup #128. Upstream replay support in redis-tower #736 is complete, but not yet consumed by the immutable pilot pin.
- Verify host-usable, deterministic executor shutdown, tracked in redis-database-mcp-rs #125.
- Record live standalone, TLS/authentication, Cluster, rollback and release evidence. Do not equate a passing compile or initial library release with production readiness.
The accepted design leaves #1171 open for remaining implementation/validation acceptance, including current-head CI and independent review. The library-side consumer tracker #76 records the host contract and evidence. The pilot and parent epic also remain open until their respective acceptance criteria are met.