agentic-community / agentic-community/mcp-gateway-registry
Resource Lifecycle Design: Draft → Review → Enabled
- Dominant language
- Python
- Stars
- 911
- Forks
- 234
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 62
Description
## Goal
When a user registers an MCP server, agent, or skill, it lands in a **proposed / draft** state. It is visible in the catalog so others don't duplicate the work, but it is **not routable and not indexed for discovery by agents** until an admin reviews metadata, runs (or verifies) a security scan, and approves.
## What already exists (and we should reuse, not reinvent)
Most of the primitives we need are already present. This design is mostly about **wiring them together into an explicit state machine**, not building new storage or auth layers.
| Need | Already exists? | Where |
|---|---|---|
| Resource-level status field | Yes. `status` enum on AgentCard supports `draft`, `active`, `deprecated`, `beta` | `registry/schemas/agent_models.py:544` |
| Default to not-routable on register | Yes. `is_enabled=False` at registration | `registry/services/agent_service.py:59` |
| Default status=draft on register | Yes. `AgentRegistrationRequest.status` defaults to `"draft"` | `registry/schemas/agent_models.py:890` |
| Separate visibility from routability | Yes. `status`/`visibility` fields are independent of `is_enabled` toggle | `interfaces.py:118` |
| Security scanner with "pending" tag | Yes. Optional on-register scan, can tag with `security-pending` | `registry/services/agent_scanner.py`, `agent_routes.py:90` |
| Admin role & granular permissions | Yes. `mcp-registry-admin` group, plus `publish_agent`, `toggle_service`, `modify_service`, `delete_agent` scopes | `registry/auth/dependencies.py` |
| Audit log infrastructure | Yes. Identity + action + authorization decision captured per request | `registry/audit/models.py`, `audit/middleware.py` |
| Manual rescan endpoint | Yes. Admin-only `POST /api/agents/{path}/rescan` | `agent_routes.py:1107` |
| Toggle enabled/disabled endpoint | Yes. `POST /api/agents/{path}/toggle?enabled=bool` | `agent_routes.py:981` |
| Agents & skills as peer resources | Yes. Not aspirational — both have routes, repositories, schemas | `registry/api/agent_routes.py`, skills routes, `schemas/skill_models.py` |
The MCP server side has the equivalent `set_state(path, enabled)` split via `ServerRepositoryBase` (`interfaces.py:118–131`), so the same pattern applies there — but we need to verify the server schema carries a `status` field or add one.
## Proposed state machine
```
[register]
|
v
+---------+ admin rejects +----------+
| draft |--------------------->| rejected |
+---------+ +----------+
| ^
| owner submits for review |
v |
+------------------+ |
| pending_review |------------------+
+------------------+
|
| (security scan passed) AND (metadata approved by admin)
v
+---------+
|approved | (is_enabled defaults to false; owner/admin toggles)
+---------+
|
| admin deprecates / retires
v
+-----------+
|deprecated |
+-----------+
```
### State definitions
- **draft** — Created by a user. Owner can edit freely. Visible in catalog to everyone (new behavior). **Not indexed in FAISS. Not routable.**
- **pending_review** — Owner has declared it ready. Edits locked (or restricted to non-material fields). Waits on admin + security-scan gates.
- **approved** — Both gates passed. Resource is now eligible to be enabled (`is_enabled` toggle works as today). Approval does *not* auto-enable — the existing toggle permission still gates routing.
- **rejected** — Admin declined. Owner can edit and resubmit (moves back to draft).
- **deprecated** — Existing state; sunset path. Already supported via `status` enum.
### Gates on the `pending_review → approved` transition
We should track these as distinct boolean/timestamp fields on the resource rather than a single `approved_at`:
- `security_scan_passed_at` (set by scanner service, or manually by admin on a `POST /.../scan-approve`)
- `metadata_approved_at` + `metadata_approved_by` (set by admin on a `POST /.../approve`)
Both must be set for the resource to move to `approved`. This means the admin UI can show "waiting on security scan" vs "waiting on admin review" distinctly, which is useful.
## API surface (additions)
Leaning on existing patterns:
| Endpoint | Who | Purpose |
|---|---|---|
| `POST /api/agents/{path}/submit-for-review` | owner | draft → pending_review |
| `POST /api/agents/{path}/approve` | admin (new scope: `approve_resource`) | sets metadata_approved_at; if scan also passed, promotes to approved |
| `POST /api/agents/{path}/reject` | admin | pending_review → rejected, with reason |
| `POST /api/agents/{path}/rescan` | admin | already exists; on pass, sets `security_scan_passed_at` |
| `GET /api/agents?status=draft` | anyone | catalog view including drafts |
Keep the existing `/toggle` endpoint. It continues to govern routing. We just add a precondition: **you cannot toggle enabled=true on a resource whose status is not `approved`.**
## Catalog visibility rules (new)
| Status | Listed in catalog? | Indexed for agent discovery (FAISS)? | Routable? |
|---|---|---|---|
| draft | Yes, with "DRAFT" badge | No | No |
| pending_review | Yes, with "UNDER REVIEW" badge | No | No |
| approved + is_enabled=false | Yes | No | No |
| approved + is_enabled=true | Yes | Yes | Yes |
| rejected | Owner/admin only | No | No |
| deprecated | Yes, with badge | Configurable | Configurable |
The UI catalog needs a visual treatment for drafts so users understand they can't use them yet but know they exist. **This is the part of the ask that directly delivers the "don't duplicate work" value.**
## Audit & history
One append-only table / collection: `resource_lifecycle_events`. Each row: resource_type, resource_path, from_state, to_state, actor, timestamp, reason (optional). This is strictly additive — the current `updated_at` / `registered_by` fields stay as-is. The existing audit middleware (`registry/audit/middleware.py`) already captures the per-request identity + action, so we may be able to *derive* these events from audit logs rather than double-writing. Worth a closer look before building a new table.
## Open questions / things to think through more
- Today, `GET /api/agents?enabled_only=true` is the default catalog-listing pattern, and disabled agents are filtered out unless the requester is admin or owner (`agent_routes.py:674–676`). If we want drafts to be **visible to everyone**, we cannot just rely on `is_enabled=False`. Suggest to stop using `is_enabled` as the visibility gate and instead use `status` (draft vs. approved) for the catalog, with `is_enabled` reserved purely for routing.
- **Editing during pending_review.** If the owner is allowed to edit freely, admin review is a moving target. Proposal: lock material fields (name, description, tools/skills, auth config) once submitted; non-material fields (icon, tags) still editable. But "material" is fuzzy — needs a real list.
- **Who can see rejected resources?** Owner + admin only seems right, but if a second user later tries to register a near-duplicate, should they see "there was a rejected one like this"? Leaning no — rejections may contain sensitive review comments.
- **Skills nested in agent registration.** If an agent is submitted, do its skills go through their own lifecycle or follow the parent? Proposal: follow the parent. Standalone skills get their own lifecycle; nested ones inherit.
- **MCP server vs. agent parity.** Verify the MCP server schema carries a `status` field; if not, add one. The routing gate (`set_state`) is there, but the catalog-status split needs confirmation.
- **Federated registry interaction.** If a draft resource is federated to a peer registry, does it propagate as draft there? Probably shouldn't federate until approved. Needs a check against federation code.
- **Security scan staleness.** Once `security_scan_passed_at` is set, does it expire? A resource approved six months ago with a stale scan is a real risk. Proposal: configurable TTL; expired → auto-revert to pending_review. But that's a follow-up, not v1.
- **Bulk migration.** Every resource today is presumably in some mix of `is_enabled=true/false` and `status=active`. Migration plan: treat all existing resources as `approved`. No resource gets retroactively demoted to draft.
- **"Claim this work" signal.** The dedup value is strongest if a draft shows *who is building it* and *how far along they are*. Today `registered_by` exists, but there's no "this person is actively working on it" signal. Surface the owner in the catalog.
Contributor guide
Assessment
This issue has not been assessed yet.