Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion cmd/publisher/commands/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,25 @@ import (

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

func InitCommand() error {
func init() {
mcpPublisherCmd.AddCommand(initCmd)
}

var initCmd = &cobra.Command{
Use: "init",
Short: "Create a server.json file template",
Long: `This command creates a server.json file in the current directory with
auto-detected values from your project (package.json, git remote, etc.).

After running init, edit the generated server.json to customize your
server's metadata before publishing.`,
RunE: runInitCmd,
}

var runInitCmd = func(_ *cobra.Command, _ []string) error {
// Check if server.json already exists
if _, err := os.Stat("server.json"); err == nil {
return errors.New("server.json already exists")
Expand Down
196 changes: 95 additions & 101 deletions cmd/publisher/commands/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/modelcontextprotocol/registry/cmd/publisher/auth"
"github.com/modelcontextprotocol/registry/cmd/publisher/auth/azurekeyvault"
"github.com/modelcontextprotocol/registry/cmd/publisher/auth/googlekms"
"github.com/spf13/cobra"
)

const (
Expand All @@ -27,6 +26,8 @@ const (

type CryptoAlgorithm auth.CryptoAlgorithm

type Token string

type SignerType string

type LoginFlags struct {
Expand All @@ -36,10 +37,9 @@ type LoginFlags struct {
KvVault string
KvKeyName string
KmsResource string
Token Token
Token string
CryptoAlgorithm CryptoAlgorithm
SignerType SignerType
ArgOffset int
}

const (
Expand All @@ -62,54 +62,81 @@ func (c *CryptoAlgorithm) Set(v string) error {
return fmt.Errorf("invalid algorithm: %q (allowed: ed25519, ecdsap384)", v)
}

type Token string

func parseLoginFlags(method string, args []string) (LoginFlags, error) {
var flags LoginFlags
loginFlags := flag.NewFlagSet("login", flag.ExitOnError)
flags.CryptoAlgorithm = CryptoAlgorithm(auth.AlgorithmEd25519)
flags.SignerType = NoSignerType
flags.ArgOffset = 1
loginFlags.StringVar(&flags.RegistryURL, "registry", DefaultRegistryURL, "Registry URL")
func (c *CryptoAlgorithm) Type() string {
return "cryptoAlgorithm"
}

// Add --token flag for GitHub authentication
var token string
if method == MethodGitHub {
loginFlags.StringVar(&token, "token", "", "GitHub Personal Access Token")
}
var flags LoginFlags

func init() {
mcpPublisherCmd.AddCommand(loginCmd)
loginCmd.Flags().StringVar(&flags.RegistryURL, "registry", DefaultRegistryURL, "Registry URL")
loginCmd.Flags().StringVarP(&flags.Token, "token", "t", "", "GitHub Personal Access Token")
loginCmd.Flags().StringVarP(&flags.Domain, "domain", "d", "", "Domain name")
loginCmd.Flags().StringVarP(&flags.KvVault, "vault", "v", "", "The name of the Azure Key Vault resource")
loginCmd.Flags().StringVarP(&flags.KvKeyName, "key", "k", "", "Name of the signing key in the Azure Key Vault")
loginCmd.Flags().StringVarP(&flags.KmsResource, "resource", "r", "", "Google Cloud KMS resource name (e.g. projects/lotr/locations/global/keyRings/fellowship/cryptoKeys/frodo/cryptoKeyVersions/1)")
loginCmd.Flags().StringVarP(&flags.PrivateKey, "private-key", "p", "", "Private key (hex)")
loginCmd.Flags().VarP(&flags.CryptoAlgorithm, "algorithm", "a", "Cryptographic algorithm (ed25519, ecdsap384)")
}

if method == "dns" || method == "http" {
loginFlags.StringVar(&flags.Domain, "domain", "", "Domain name")
if len(args) > 1 {
switch args[1] {
case string(AzureKeyVaultSignerType):
flags.SignerType = AzureKeyVaultSignerType
loginFlags.StringVar(&flags.KvVault, "vault", "", "The name of the Azure Key Vault resource")
loginFlags.StringVar(&flags.KvKeyName, "key", "", "Name of the signing key in the Azure Key Vault")
flags.ArgOffset = 2
case string(GoogleKMSSignerType):
flags.SignerType = GoogleKMSSignerType
loginFlags.StringVar(&flags.KmsResource, "resource", "", "Google Cloud KMS resource name (e.g. projects/lotr/locations/global/keyRings/fellowship/cryptoKeys/frodo/cryptoKeyVersions/1)")
flags.ArgOffset = 2
}
var loginCmd = &cobra.Command{
Use: "login <method> [options]",
Short: "Authenticate with the registry",
Long: `Methods:
github Interactive GitHub authentication
github-oidc GitHub Actions OIDC authentication
dns DNS-based authentication (requires --domain)
http HTTP-based authentication (requires --domain)
none Anonymous authentication (for testing)`,
Args: func(_ *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New(`authentication method required

Usage: mcp-publisher login <method> [<signing provider>]

Methods:
github Interactive GitHub authentication
github-oidc GitHub Actions OIDC authentication
dns DNS-based authentication (requires --domain)
http HTTP-based authentication (requires --domain)
none Anonymous authentication (for testing)

Signing providers:
azure-key-vault Sign using Azure Key Vault
google-kms Sign using Google Cloud KMS

The dns and http methods require a --private-key for in-process signing. For
out-of-process signing, use one of the supported signing providers. Signing is
needed for an authentication challenge with the registry.

The github and github-oidc methods do not support signing providers and
authenticate using the GitHub as an identity provider.

Examples:
# Interactive GitHub login, using device code flow
mcp-publisher login github

# Sign in using a specific Ed25519 private key for DNS authentication
mcp-publisher login dns -algorithm ed25519 -domain example.com -private-key <64 hex chars>

# Sign in using a specific ECDSA P-384 private key for DNS authentication
mcp-publisher login dns -algorithm ecdsap384 -domain example.com -private-key <96 hex chars>

# Sign in with gcloud CLI, use Google Cloud KMS for signing in DNS authentication
gcloud auth application-default login
mcp-publisher login dns google-kms -domain example.com -resource projects/lotr/locations/global/keyRings/fellowship/cryptoKeys/frodo/cryptoKeyVersions/1

# Sign in with az CLI, use Azure Key Vault for signing in HTTP authentication
az login
mcp-publisher login http azure-key-vault -domain example.com -vault myvault -key mysigningkey`)
}
if flags.SignerType == NoSignerType {
flags.SignerType = InProcessSignerType
loginFlags.StringVar(&flags.PrivateKey, "private-key", "", "Private key (hex)")
loginFlags.Var(&flags.CryptoAlgorithm, "algorithm", "Cryptographic algorithm (ed25519, ecdsap384)")
}
}
err := loginFlags.Parse(args[flags.ArgOffset:])
if err == nil {
flags.RegistryURL = strings.TrimRight(flags.RegistryURL, "/")
}

// Store the token in flags if it was provided
if method == MethodGitHub {
flags.Token = Token(token)
}

return flags, err
return nil
},
Example: `
mcp-publisher login github
mcp-publisher login dns --domain example.com --private-key <key>`,
RunE: runLoginCommand,
}

func createSigner(flags LoginFlags) (auth.Signer, error) {
Expand All @@ -127,10 +154,10 @@ func createSigner(flags LoginFlags) (auth.Signer, error) {
}
}

func createAuthProvider(method, registryURL, domain string, token Token, signer auth.Signer) (auth.Provider, error) {
func createAuthProvider(method, registryURL, domain string, token string, signer auth.Signer) (auth.Provider, error) {
switch method {
case MethodGitHub:
return auth.NewGitHubATProvider(true, registryURL, string(token)), nil
return auth.NewGitHubATProvider(true, registryURL, token), nil
case MethodGitHubOIDC:
return auth.NewGitHubOIDCProvider(registryURL), nil
case MethodDNS:
Expand All @@ -150,59 +177,26 @@ func createAuthProvider(method, registryURL, domain string, token Token, signer
}
}

func LoginCommand(args []string) error {
if len(args) < 1 {
return errors.New(`authentication method required

Usage: mcp-publisher login <method> [<signing provider>]

Methods:
github Interactive GitHub authentication
github-oidc GitHub Actions OIDC authentication
dns DNS-based authentication (requires --domain)
http HTTP-based authentication (requires --domain)
none Anonymous authentication (for testing)

Signing providers:
azure-key-vault Sign using Azure Key Vault
google-kms Sign using Google Cloud KMS

The dns and http methods require a --private-key for in-process signing. For
out-of-process signing, use one of the supported signing providers. Signing is
needed for an authentication challenge with the registry.

The github and github-oidc methods do not support signing providers and
authenticate using the GitHub as an identity provider.

Examples:

# Interactive GitHub login, using device code flow
mcp-publisher login github

# Sign in using a specific Ed25519 private key for DNS authentication
mcp-publisher login dns -algorithm ed25519 -domain example.com -private-key <64 hex chars>

# Sign in using a specific ECDSA P-384 private key for DNS authentication
mcp-publisher login dns -algorithm ecdsap384 -domain example.com -private-key <96 hex chars>

# Sign in with gcloud CLI, use Google Cloud KMS for signing in DNS authentication
gcloud auth application-default login
mcp-publisher login dns google-kms -domain example.com -resource projects/lotr/locations/global/keyRings/fellowship/cryptoKeys/frodo/cryptoKeyVersions/1

# Sign in with az CLI, use Azure Key Vault for signing in HTTP authentication
az login
mcp-publisher login http azure-key-vault -domain example.com -vault myvault -key mysigningkey

`)
}

var runLoginCommand = func(_ *cobra.Command, args []string) error {
var (
signer auth.Signer
err error
)
method := args[0]
flags, err := parseLoginFlags(method, args)
if err != nil {
return err
flags.SignerType = NoSignerType
if method == "http" || method == "dns" {
if len(args) > 1 {
switch args[1] {
case string(AzureKeyVaultSignerType):
flags.SignerType = AzureKeyVaultSignerType
case string(GoogleKMSSignerType):
flags.SignerType = GoogleKMSSignerType
}
} else {
flags.SignerType = InProcessSignerType
}
}

var signer auth.Signer
if flags.SignerType != NoSignerType {
signer, err = createSigner(flags)
if err != nil {
Expand Down
15 changes: 14 additions & 1 deletion cmd/publisher/commands/logout.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,22 @@ import (
"fmt"
"os"
"path/filepath"

"github.com/spf13/cobra"
)

func LogoutCommand() error {
func init() {
mcpPublisherCmd.AddCommand(logoutCmd)
}

var logoutCmd = &cobra.Command{
Use: "logout",
Short: "Clear saved authentication",
Long: `This command removes the saved authentication token from your system.`,
RunE: LogoutCommand,
}

var LogoutCommand = func(_ *cobra.Command, _ []string) error {
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to get home directory: %w", err)
Expand Down
17 changes: 16 additions & 1 deletion cmd/publisher/commands/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,24 @@ import (
"strings"

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

func PublishCommand(args []string) error {
func init() {
mcpPublisherCmd.AddCommand(publishCmd)
}

var publishCmd = &cobra.Command{
Use: "publish [server.json]",
Short: "Publish server.json to the registry",
Long: `Arguments:
server.json Path to the server.json file (default: ./server.json)
You must be logged in before publishing. Run 'mcp-publisher login' first.`,
RunE: RunPublishCommand,
}

var RunPublishCommand = func(_ *cobra.Command, args []string) error {
// Check for server.json file
serverFile := "server.json"
if len(args) > 0 && !strings.HasPrefix(args[0], "-") {
Expand Down
12 changes: 6 additions & 6 deletions cmd/publisher/commands/publish_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestPublishCommand_Success(t *testing.T) {
CreateTestServerJSON(t, serverJSON)

// Run publish command
err := commands.PublishCommand([]string{})
err := commands.RunPublishCommand(nil, []string{})

// Should succeed
assert.NoError(t, err)
Expand Down Expand Up @@ -89,7 +89,7 @@ func TestPublishCommand_422ValidationFlow(t *testing.T) {
CreateTestServerJSON(t, serverJSON)

// Run publish command
err := commands.PublishCommand([]string{})
err := commands.RunPublishCommand(nil, []string{})

// Should fail with validation error
require.Error(t, err)
Expand Down Expand Up @@ -149,7 +149,7 @@ func TestPublishCommand_422WithMultipleIssues(t *testing.T) {
}
CreateTestServerJSON(t, serverJSON)

err := commands.PublishCommand([]string{})
err := commands.RunPublishCommand(nil, []string{})

require.Error(t, err)
assert.Equal(t, 1, validateCallCount, "validate endpoint should be called")
Expand All @@ -165,7 +165,7 @@ func TestPublishCommand_NoToken(t *testing.T) {
}
CreateTestServerJSON(t, serverJSON)

err := commands.PublishCommand([]string{})
err := commands.RunPublishCommand(nil, []string{})

require.Error(t, err)
assert.Contains(t, err.Error(), "not authenticated")
Expand Down Expand Up @@ -193,7 +193,7 @@ func TestPublishCommand_Non422Error(t *testing.T) {
}
CreateTestServerJSON(t, serverJSON)

err := commands.PublishCommand([]string{})
err := commands.RunPublishCommand(nil, []string{})

require.Error(t, err)
assert.Contains(t, err.Error(), "publish failed")
Expand Down Expand Up @@ -337,7 +337,7 @@ func TestPublishCommand_DeprecatedSchema(t *testing.T) {
}
CreateTestServerJSON(t, serverJSON)

err := commands.PublishCommand([]string{})
err := commands.RunPublishCommand(nil, []string{})

if tt.expectError {
require.Error(t, err, "Expected error for test case: %s", tt.name)
Expand Down
Loading
Loading