agentic-community / agentic-community/mcp-gateway-registry

Resource Lifecycle Design: Draft → Review → Enabled

Open
#914 0 comments 0 reactions 0 assignees View on GitHub
enhancement
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.