registry/docs/administration/admin-operations.md
Radoslav Dimitrov 43fbc1dcbd
fix(admin): use PATCH status endpoints in takedown script and runbook (#1518)
The admin takedown script cannot work as written, and the runbook that
documents it has the same problem.

## The bug

`tools/admin/takedown.sh` sent `PUT` with a `?status=deleted` query
parameter to two routes:

| What the script sent | Reality |
|---|---|
| `PUT /v0/servers/{name}/versions/{version}?status=deleted` | Route
exists, but requires a full `ServerJSON` body and has no `status` query
parameter |
| `PUT /v0/servers/{name}?status=deleted` | **Not registered at all** —
the whole-server takedown path 404s |

The real endpoints are `PATCH
/v0/servers/{name}/versions/{version}/status` and `PATCH
/v0/servers/{name}/status`
([status.go:83](https://github.com/modelcontextprotocol/registry/blob/main/internal/api/handlers/v0/status.go#L83),
[:198](https://github.com/modelcontextprotocol/registry/blob/main/internal/api/handlers/v0/status.go#L198)),
both taking `{"status": ..., "statusMessage": ...}`. `edit.go` even says
so in its own description: use the status endpoint for status changes.

## Script changes

- Send `PATCH` with a JSON body built via `jq`
- **Require `VERSION` or `ALL_VERSIONS=true` explicitly.** Previously a
missing `VERSION` meant "entire server". Since `PATCH .../status`
updates *every* version, an operator who simply forgot `VERSION` would
have taken down the whole server. Now that needs saying out loud.
- Support `STATUS` (default `deleted`) and `STATUS_MESSAGE`, so a
takedown can record its reason
- `set -euo pipefail`, non-zero exit on HTTP errors, and print the
response body so huma error detail is visible instead of being swallowed

## Runbook changes

- `GET /v0/servers/{name}` is not a route — use `GET
.../versions/latest` (which is supported, see
[servers.go:155-178](https://github.com/modelcontextprotocol/registry/blob/main/internal/api/handlers/v0/servers.go#L155))
- Replace the per-version `?status=deleted` loop with the single
all-versions `PATCH .../status` call, which reports `updatedCount`
- Keep the per-version `PUT` loop for *content* edits, which genuinely
has no bulk endpoint, and show that it needs the full body
- Correct the closing note claiming server-wide changes must be applied
per version — true for content, not for status
- Fix the broken `moderation-guidelines.md` link (dead since the docs
restructuring; the file is now
`modelcontextprotocol-io/moderation-policy.mdx`)

## Verification

`shellcheck` clean, `bash -n` clean, and all three guard paths exercised
(no args / neither VERSION nor ALL_VERSIONS / both set). Both `PATCH`
paths confirmed present in `docs/reference/api/openapi.yaml`. I did not
run a live takedown against production.

Kept on `/v0` rather than `/v0.1` to match the existing admin/deploy
convention — happy to switch if you would rather admin tooling target
the frozen version.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 16:24:06 +03:00

7.6 KiB

Admin Operations

This is a brief guide for admins and moderators managing content on the registry. All actions should be taken in line with the moderation policy.

Prerequisites

  • Admin account with @modelcontextprotocol.io email
    • If you are a maintainer and would like an account, ask in the Discord
  • gcloud CLI installed and configured
  • curl and jq installed
  • kubectl installed with gke-gcloud-auth-plugin (for database access)

Authentication

# Run this, then run the export command it outputs
./tools/admin/auth.sh

Edit a Specific Server Version

Use this when you need to modify details of a specific version (e.g., fix description, update status, modify packages).

Step 1: Download Specific Version

export SERVER_NAME="<server-name>"    # e.g., "com.example/my-server"
export VERSION="<version-string>"     # e.g., "1.0.0" (optional, defaults to latest)

# URL encode the server name (replace / with %2F)
ENCODED_SERVER_NAME=$(echo "$SERVER_NAME" | sed 's|/|%2F|g')

# Get specific version
curl -s "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/${VERSION}" > server.json

# Or get the latest version (use the special version "latest")
curl -s "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/latest" > server.json

Step 2: Make Changes

Open server.json and edit the specific version details. You cannot change the server name or version number.

Step 3: Update Version

# Update specific version (requires the full server.json body)
curl -X PUT "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/${VERSION}" \
  -H "Authorization: Bearer ${REGISTRY_TOKEN}" \
  -H "Content-Type: application/json" \
  -d "$(cat server.json)"

To change only the status of a version, use the status endpoint instead — it does not require the full server configuration:

curl -X PATCH "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/${VERSION}/status" \
  -H "Authorization: Bearer ${REGISTRY_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"status": "deprecated", "statusMessage": "Superseded by v2"}'

Edit an Entire Server (All Versions)

Status Changes Across All Versions

A status change applies to every version of a server in a single request. The response reports how many versions were updated in updatedCount.

export SERVER_NAME="<server-name>"    # e.g., "com.example/my-server"
ENCODED_SERVER_NAME=$(echo "$SERVER_NAME" | sed 's|/|%2F|g')

curl -X PATCH "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/status" \
  -H "Authorization: Bearer ${REGISTRY_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"status": "deleted", "statusMessage": "Removed per moderation policy"}'

Content Changes Across All Versions

Content edits (e.g. scrubbing sensitive text from descriptions) have no bulk endpoint and must be applied per version using the edit endpoint, which takes the full server configuration.

Step 1: List All Versions

export SERVER_NAME="<server-name>"    # e.g., "com.example/my-server"
ENCODED_SERVER_NAME=$(echo "$SERVER_NAME" | sed 's|/|%2F|g')

curl -s "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions" > all_versions.json

Step 2: Extract Versions

# Extract all versions from the server
jq -r '.servers[].server.version' all_versions.json > versions.txt

Step 3: Apply Changes to Each Version

while read VERSION; do
  echo "Processing version: $VERSION"

  # Download the version, edit it, then send the full body back
  curl -s "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/${VERSION}" > version.json

  # Apply your changes to version.json here, then:
  curl -X PUT "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/${VERSION}" \
    -H "Authorization: Bearer ${REGISTRY_TOKEN}" \
    -H "Content-Type: application/json" \
    -d "$(cat version.json)"

done < versions.txt

# Clean up temporary files
rm -f versions.txt all_versions.json version.json

Quick Operations

Get Latest Version of a Server

export SERVER_NAME="<server-name>"    # e.g., "com.example/my-server"
ENCODED_SERVER_NAME=$(echo "$SERVER_NAME" | sed 's|/|%2F|g')

curl -s "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/latest" > latest_version.json
export VERSION=$(jq -r '.server.version' latest_version.json)
echo "Latest version: $VERSION"

Takedown a Specific Version

export SERVER_NAME="<server-name>"    # e.g., "com.example/my-server"
export VERSION="<version-string>"     # e.g., "1.0.0"
export REGISTRY_TOKEN="<your-token>"

REGISTRY_TOKEN="$REGISTRY_TOKEN" SERVER_NAME="$SERVER_NAME" VERSION="$VERSION" ./tools/admin/takedown.sh

Takedown All Versions of a Server

ALL_VERSIONS=true marks every version as deleted in a single request. The script requires either VERSION or ALL_VERSIONS to be set explicitly, so a forgotten VERSION cannot take down a whole server by accident.

export SERVER_NAME="<server-name>"    # e.g., "com.example/my-server"
export REGISTRY_TOKEN="<your-token>"

REGISTRY_TOKEN="$REGISTRY_TOKEN" SERVER_NAME="$SERVER_NAME" ALL_VERSIONS=true ./tools/admin/takedown.sh

Takedown the Latest Version Only

export SERVER_NAME="<server-name>"    # e.g., "com.example/my-server"
export REGISTRY_TOKEN="<your-token>"
ENCODED_SERVER_NAME=$(echo "$SERVER_NAME" | sed 's|/|%2F|g')

# Resolve the latest version, then take down that specific version
VERSION=$(curl -s "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/latest" | jq -r '.server.version')

REGISTRY_TOKEN="$REGISTRY_TOKEN" SERVER_NAME="$SERVER_NAME" VERSION="$VERSION" ./tools/admin/takedown.sh

Record a Reason with a Takedown

REGISTRY_TOKEN="$REGISTRY_TOKEN" SERVER_NAME="$SERVER_NAME" ALL_VERSIONS=true \
  STATUS_MESSAGE="Removed per moderation policy" ./tools/admin/takedown.sh

Connecting to the Production Database

For debugging or data analysis, you can connect directly to the production PostgreSQL database. Use caution and prefer read-only access.

Prerequisites

Install the GKE auth plugin if you haven't already:

gcloud components install gke-gcloud-auth-plugin

Connect

# Get cluster credentials
gcloud container clusters get-credentials mcp-registry-prod --zone us-central1-b --project mcp-registry-prod

# Get the database password
kubectl get secret registry-pg-app -o jsonpath='{.data.password}' | base64 -d

# Port-forward and connect (enter the password from above)
kubectl port-forward svc/registry-pg-rw 15432:5432 &
sleep 2
psql -h localhost -p 15432 -U app -d app

Read-Only Access

To prevent accidental writes, set your session to read-only after connecting:

SET default_transaction_read_only = on;

Any write attempts will fail with an error until you disconnect.

Notes

  • Version-specific changes: Only affect that particular version
  • Server-wide status changes: PATCH /v0/servers/{serverName}/status updates every version in one request
  • Server-wide content changes: Have no bulk endpoint and must be applied to each version individually
  • Status vs. edit: Use PATCH .../status to change status alone; the PUT edit endpoint requires the full server configuration
  • Content scrubbing: Use the version-specific edit workflow to scrub sensitive content
  • Server name: Cannot be changed in any version (it's the immutable identifier)