Skip to content

Conversation

@pree-dew
Copy link
Contributor

Motivation and Context

Deprecation and yanking are standard requirements when it comes to manages servers(packages), many use cases like rebranding, security vulnerabilities in packages etc demand these kind of features.

How Has This Been Tested?

This has been tested via test cases and local setup

Breaking Changes

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

@pree-dew
Copy link
Contributor Author

Fixes #637

Copy link
Member

@tadasant tadasant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @pree-dew for taking this on!

Added inline comments, and also: let's update docs/reference/cli/commands.md with notes on how to use the new CLI commands/options.

Some additional feedback by way of Claude (that I've picked out and agree with):

1. newName pointing to itself

There's no check preventing:

mcp-publisher status io.github.user/my-server --all-versions --status deprecated \
  --new-name io.github.user/my-server

The validateNewName function checks that the new server exists and user has permissions, but doesn't check if newName == currentServerName. This would create a self-referential deprecation.

2. --all-versions isn't atomic

Looking at updateAllVersionsStatus (lines 123-157):

for _, v := range versions {
    if err := updateServerStatus(...); err != nil {
        failureCount++
    } else {
        successCount++
    }
}

Each version is updated in a separate HTTP request. If one fails mid-way:

  • Some versions are deprecated, others aren't
  • No rollback mechanism
  • The newName could be set on some versions but not others

Can we make this happen in a transaction?

3. newName without --all-versions at API level

The CLI enforces --new-name requires --all-versions, but the API doesn't:

// In validateNewName - only checks status, not all-versions
if input.Status != string(model.StatusDeprecated) && input.Status != string(model.StatusYanked) {
    return huma.Error400BadRequest("new_name can only be used with deprecated or yanked status")
}

Someone hitting the API directly could set newName on a single version, leaving other versions without it. This creates inconsistent state.

StatusActive Status = "active"
StatusDeprecated Status = "deprecated"
StatusDeleted Status = "deleted"
StatusYanked Status = "yanked"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I agree with the semantic reason for this change, I worry a bit about this being a breaking change. I think we don't currently have any data in the official registry using deleted, but I suspect a lot of downstream consumers do.

So by default, I'd be inclined to keep it deleted (knowing we really mean yanked), and maybe leave an issue as something we consider addressing for a bigger, breaking GA launch of the registry.

Status model.Status `json:"status" enum:"active,deprecated,yanked" doc:"Server lifecycle status"`
StatusChangedAt time.Time `json:"statusChangedAt" format:"date-time" doc:"Timestamp when the server status was last changed"`
StatusMessage *string `json:"statusMessage,omitempty" doc:"Optional message explaining status change (e.g., deprecation reason, migration guidance)"`
AlternativeURL *string `json:"alternativeUrl,omitempty" format:"uri" doc:"Optional URL to alternative/replacement server for deprecated or yanked servers"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need AlternativeURL in addition to StatusMessage and NewName? It feels redundant to me, and if someone really wants a non-registry reference (which would be NewName), they could put the URL in the StatusMessage. Or is there compelling reason to have the structured AlternativeURL?

Comment on lines 115 to +117
// For now, only allow status changes for admins
// Future: Implement logic to allow server authors to change active <-> deprecated
// but only admins can set to deleted
// but only admins can set to yanked
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is now outdated; should delete

Authorization string `header:"Authorization" doc:"Registry JWT token with edit permissions" required:"true"`
ServerName string `path:"serverName" doc:"URL-encoded server name" example:"com.example%2Fmy-server"`
Version string `path:"version" doc:"URL-encoded version to edit" example:"1.0.0"`
Status string `query:"status" doc:"New status for the server (active, deprecated, yanked)" required:"false" enum:"active,deprecated,yanked"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This endpoint design feels awkward to me. I know we had precedent here with status already, but I think overloading this shape with being responsible for both Body (core ServerJSON) changes as well as status & associated fields (statusmessage, newname)

Should we move status wrangling over to a dedicated PATCH endpoint?

To-date, status has only been an admin tool anyway, so I'm not worried about a breaking change in the API here.

apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
)

func StatusCommand(args []string) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about calling this command status. It's also used for setting e.g. newName, which isn't necessarily associated with a status change. Can we make it more generic, like mcp-publisher update, mcp-publisher patch or something similar?

@pree-dew
Copy link
Contributor Author

@tadasant Thank you for the review, I will address the points.

Also wanted your feedback on these questions:

Do we need bulk endpoint for status transitions, renaming for all servers?
Do we want to allow publisher to make status transition?
Do we want yanked package discoverable?

@tadasant
Copy link
Member

Do we need bulk endpoint for status transitions, renaming for all servers?

If I understand the question correctly, we'd want the ability to e.g. "deprecate all versions of server with name x" for the case where we want to deprecate --all-versions; yes. We wouldn't need something like "deprecate all versions of all servers with names x, y, and z all together in one API call".

Do we want to allow publisher to make status transition?

Yes.

Do we want yanked package discoverable?

Ideally: it should show up on /servers?updated_since calls, but not by default on server details / server version history calls unless we provide some explicit query param that "unhides" them.

@pree-dew
Copy link
Contributor Author

If I understand the question correctly, we'd want the ability to e.g. "deprecate all versions of server with name x" for the case where we want to deprecate --all-versions; yes.

Yes, for all versions of a server. Noted.

I will accommodate recommended changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants