You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add draft status as default for new MCP server versions
New versions now default to draft instead of active, requiring an
explicit publish action (draft → active) before downstream consumers
can discover them. The Phase 2 upstream compatibility router filters
out draft versions since the upstream MCP registry spec only defines
active/deprecated/deleted.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Jon Burdo <jon@jonburdo.com>
Copy file name to clipboardExpand all lines: rfcs/0004-mcp-registry/0004-mcp-registry.md
+31-16Lines changed: 31 additions & 16 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -81,10 +81,17 @@ version = mlflow.genai.register_mcp_server(
81
81
],
82
82
},
83
83
)
84
-
# version.status == "active"
84
+
# version.status == "draft" (default — not yet visible to downstream consumers)
85
85
# version.version == "1.0.0" (extracted from server_json)
86
86
# version.name == "io.github.anthropic/brave-search" (extracted from server_json)
87
87
88
+
# Publish the version to make it discoverable
89
+
mlflow.genai.update_mcp_server_version(
90
+
name="io.github.anthropic/brave-search",
91
+
version="1.0.0",
92
+
status="active",
93
+
)
94
+
88
95
# Set an alias for stable resolution
89
96
mlflow.genai.set_mcp_server_alias(
90
97
name="io.github.anthropic/brave-search",
@@ -152,7 +159,7 @@ This RFC defines the registry layer first. It does **not** yet design the MLflow
152
159
### Use cases
153
160
154
161
1.**Governed registration**: Platform administrators register MCP server definitions (both internally developed packages and hosted remote endpoints) as governed, versioned assets with stable identity
155
-
2.**Lifecycle management**: MCP server versions move through statuses (active → deprecated → deleted) to control downstream surfacing
162
+
2.**Lifecycle management**: MCP server versions move through statuses (draft → active → deprecated → deleted) to control downstream surfacing
156
163
3.**Discovery and resolution**: MLflow-aware clients and runtimes discover active MCP servers and resolve them by name + version or alias
157
164
4.**Direct connectivity association**: Operators can record approved direct connection paths as first-class binding records, bridging the gap between "what is governed" and "what is live"
158
165
5.**Version history**: Multiple versions of an MCP server coexist with independent lifecycle states, supporting deprecation without erasing history
@@ -226,7 +233,7 @@ This design aligns with the [upstream MCP registry specification](https://regist
226
233
227
234
-**Endpoint mapping**: all 8 upstream endpoints map closely to the MLflow-native API shape. The main nuance is upstream version update: the upstream spec includes an optional in-place version update endpoint, but the official MCP registry does not implement it. In MLflow, canonical publisher-managed `server_json` changes are represented by registering a new version, while mutable MLflow-specific fields remain editable through MLflow-native update APIs.
228
235
-**Response translation**: wrap MLflow entities in the upstream `{servers: [{server, _meta}]}` envelope, with MLflow-specific metadata (tags, aliases, workspace) in a custom `_meta` namespace (e.g., `org.mlflow`)
229
-
-**Status mapping**: MLflow uses upstream status values (`active`/`deprecated`/`deleted`) directly. MLflow's `deleted` is a soft delete — records are preserved for history
236
+
-**Status mapping**: MLflow extends the upstream status values (`active`/`deprecated`/`deleted`) with an additional `draft` state for staging versions before publication. The upstream spec does not define a draft state, so the Phase 2 compatibility router will need to decide how to handle it — for example, filtering `draft` versions from read responses and auto-publishing versions created through the upstream write API, or exposing `draft` as a non-standard extension. The exact mapping is deferred to Phase 2 design. MLflow's `deleted` is a soft delete — records are preserved for history
230
237
-**Pagination**: cursor ↔ page_token translation
231
238
-**Workspace**: MLflow's existing middleware already extracts `X-MLFLOW-WORKSPACE` from incoming request headers, so external clients pass the workspace header alongside standard Bearer auth — no protocol changes needed
232
239
@@ -288,6 +295,7 @@ A versioned record containing an immutable MCP payload and mutable MLflow-manage
288
295
289
296
```python
290
297
classMCPStatus(StrEnum):
298
+
DRAFT="draft"# registered but not yet ready for downstream consumption
291
299
ACTIVE="active"
292
300
DEPRECATED="deprecated"
293
301
DELETED="deleted"# soft delete — retained internally for history, excluded from normal get/search/list APIs
@@ -299,7 +307,7 @@ class MCPServerVersion:
299
307
version: str# extracted from server_json["version"]; semver recommended
source: str|None=None# provenance URI (e.g., git repository URL)
@@ -448,14 +456,18 @@ Each `MCPServerVersion` has an independent status controlling downstream surfaci
448
456
449
457
```mermaid
450
458
stateDiagram-v2
451
-
[*] --> active
459
+
[*] --> draft
460
+
draft --> active : publish
461
+
draft --> deleted : discard
462
+
active --> draft : unpublish
452
463
active --> deprecated
453
464
deprecated --> active : re-activate
454
465
deprecated --> deleted
455
466
```
456
467
457
468
| State | Meaning | Downstream surfacing |
458
469
|---|---|---|
470
+
|`draft`| Registered but not yet ready for downstream consumption | Not surfaced — invisible to catalogs, gateways, consumers |
459
471
|`active`| Ready for downstream use | Surfaced to catalogs, gateways, consumers |
460
472
|`deprecated`| Still functional but no longer recommended | Surfaced with deprecation signal |
461
473
|`deleted`| Soft-deleted — record preserved for history, no longer active | Not surfaced |
@@ -464,10 +476,13 @@ stateDiagram-v2
464
476
465
477
| From | To |
466
478
|---|---|
467
-
|`active`|`deprecated`|
479
+
|`draft`|`active`, `deleted`|
480
+
|`active`|`draft`, `deprecated`|
468
481
|`deprecated`|`active`, `deleted`|
469
482
470
-
`deprecated` can return to `active` (re-activate) to handle cases where a deprecation was premature or a planned replacement is not yet ready.
483
+
`draft` lets teams stage MCP server versions — validating metadata, setting aliases, and configuring access bindings — before making them discoverable. Transitioning from `draft` to `active` is an explicit publish action. A draft can also be discarded directly (`draft` → `deleted`) without ever being published. `active` can return to `draft` (unpublish) when a version was published prematurely or needs rework.
484
+
485
+
`deprecated` can return to `active` (re-activate) to handle cases where a deprecation was premature or a planned replacement is not yet ready. `deleted` is terminal.
471
486
472
487
#### Server-level status (derived)
473
488
@@ -501,7 +516,7 @@ Six tables, created via a single Alembic migration. All tables are workspace-sco
status: MCPStatus |None=None, # defaults to ACTIVE
648
+
status: MCPStatus |None=None, # defaults to DRAFT
634
649
) -> MCPServerVersion:
635
650
raiseNotImplementedError(self.__class__.__name__)
636
651
@@ -727,9 +742,9 @@ class MCPServerRegistryMixin:
727
742
728
743
**User-facing vs. store layer**: Following the general shape of MLflow model registry, the Python SDK exposes explicit create/get/search/update/delete operations for the core entities. On top of that, it also provides `register_mcp_server(...)` and `register_mcp_server_from_url(...)` as convenience helpers for the common "ingest a canonical `server.json` and create or update the parent server as needed" workflow. Internally, these helpers call the same underlying `create_mcp_server()` / `create_mcp_server_version()` flow. The URL helper is client-side and fetches the canonical `server.json` over HTTPS before calling the same registration path.
729
744
730
-
**Name and version extraction**: `create_mcp_server_version` extracts both `name` and `version` from `server_json` at the store layer. In the native REST API, version creation is nested under `/{name}/versions`; `server_json["name"]` must match the path parameter, and the matching parent `MCPServer` is looked up or auto-created if needed. If either `name` or `version` is missing from `server_json`, creation fails with a validation error. New versions default to `active` status.
745
+
**Name and version extraction**: `create_mcp_server_version` extracts both `name` and `version` from `server_json` at the store layer. In the native REST API, version creation is nested under `/{name}/versions`; `server_json["name"]` must match the path parameter, and the matching parent `MCPServer` is looked up or auto-created if needed. If either `name` or `version` is missing from `server_json`, creation fails with a validation error. New versions default to `draft` status.
731
746
732
-
**Status transition enforcement**: `update_mcp_server_version` validates that status transitions follow the allowed paths (active→deprecated, deprecated→active, deprecated→deleted). New versions default to `active`; any later status change is an explicit admin action rather than automatic MLflow behavior.
747
+
**Status transition enforcement**: `update_mcp_server_version` validates that status transitions follow the allowed paths (draft→active, draft→deleted, active→draft, active→deprecated, deprecated→active, deprecated→deleted). `deleted` is terminal. New versions default to `draft`; transitioning to `active` is an explicit publish action, and any later status change is likewise an explicit admin action rather than automatic MLflow behavior.
733
748
734
749
**Latest version**: `get_latest_mcp_server_version` checks `MCPServer.latest_version_alias` first — if set, it resolves that alias to a version. If unset, it falls back to the version with the most recent `creation_timestamp`. This lets users explicitly control what "latest" means (e.g., pointing it at the latest *active* version) while preserving a sensible default.
735
750
@@ -793,7 +808,7 @@ class UpdateMCPServerRequest(BaseModel):
793
808
classCreateMCPServerVersionRequest(BaseModel):
794
809
server_json: ServerJSONPayload
795
810
display_name: str|None=None
796
-
status: str="active"
811
+
status: str="draft"
797
812
source: str|None=None
798
813
799
814
@@ -847,7 +862,7 @@ class MCPServerVersionResponse(BaseModel):
@@ -1169,4 +1184,4 @@ Each phase is independently useful. Phase 1 delivers a complete, self-contained
1169
1184
1170
1185
# Open questions
1171
1186
1172
-
1.Should we add a `draft` status for versions that are registered but not yet ready for consumption? This would let teams stage MCP server versions before making them discoverable. The tradeoff is that the upstream MCP registry spec only defines `active`, `deprecated`, and `deleted` — adding `draft`would be an MLflow extension that the compatibility layer would need to hide from upstream clients.
1187
+
1.How should the Phase 2 upstream compatibility router handle MLflow's `draft` status? The upstream MCP registry spec only defines `active`, `deprecated`, and `deleted`. Options include filtering `draft`versions from read responses and auto-publishing versions created through the upstream write API, or exposing `draft` as a non-standard extension. Deferred to Phase 2 design.
0 commit comments