Skip to content

Commit 769d31f

Browse files
committed
Move the CLI to Cobra framework
1 parent cb4807a commit 769d31f

File tree

11 files changed

+327
-367
lines changed

11 files changed

+327
-367
lines changed

cmd/publisher/commands/init.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,25 @@ import (
1313

1414
apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
1515
"github.com/modelcontextprotocol/registry/pkg/model"
16+
"github.com/spf13/cobra"
1617
)
1718

18-
func InitCommand() error {
19+
func init() {
20+
mcpPublisherCmd.AddCommand(initCmd)
21+
}
22+
23+
var initCmd = &cobra.Command{
24+
Use: "init",
25+
Short: "Create a server.json file template",
26+
Long: `This command creates a server.json file in the current directory with
27+
auto-detected values from your project (package.json, git remote, etc.).
28+
29+
After running init, edit the generated server.json to customize your
30+
server's metadata before publishing.`,
31+
RunE: runInitCmd,
32+
}
33+
34+
var runInitCmd = func(_ *cobra.Command, _ []string) error {
1935
// Check if server.json already exists
2036
if _, err := os.Stat("server.json"); err == nil {
2137
return errors.New("server.json already exists")

cmd/publisher/commands/login.go

Lines changed: 95 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ import (
44
"context"
55
"encoding/json"
66
"errors"
7-
"flag"
87
"fmt"
98
"os"
109
"path/filepath"
11-
"strings"
1210

1311
"github.com/modelcontextprotocol/registry/cmd/publisher/auth"
1412
"github.com/modelcontextprotocol/registry/cmd/publisher/auth/azurekeyvault"
1513
"github.com/modelcontextprotocol/registry/cmd/publisher/auth/googlekms"
14+
"github.com/spf13/cobra"
1615
)
1716

1817
const (
@@ -27,6 +26,8 @@ const (
2726

2827
type CryptoAlgorithm auth.CryptoAlgorithm
2928

29+
type Token string
30+
3031
type SignerType string
3132

3233
type LoginFlags struct {
@@ -36,10 +37,9 @@ type LoginFlags struct {
3637
KvVault string
3738
KvKeyName string
3839
KmsResource string
39-
Token Token
40+
Token string
4041
CryptoAlgorithm CryptoAlgorithm
4142
SignerType SignerType
42-
ArgOffset int
4343
}
4444

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

65-
type Token string
66-
67-
func parseLoginFlags(method string, args []string) (LoginFlags, error) {
68-
var flags LoginFlags
69-
loginFlags := flag.NewFlagSet("login", flag.ExitOnError)
70-
flags.CryptoAlgorithm = CryptoAlgorithm(auth.AlgorithmEd25519)
71-
flags.SignerType = NoSignerType
72-
flags.ArgOffset = 1
73-
loginFlags.StringVar(&flags.RegistryURL, "registry", DefaultRegistryURL, "Registry URL")
65+
func (c *CryptoAlgorithm) Type() string {
66+
return "cryptoAlgorithm"
67+
}
7468

75-
// Add --token flag for GitHub authentication
76-
var token string
77-
if method == MethodGitHub {
78-
loginFlags.StringVar(&token, "token", "", "GitHub Personal Access Token")
79-
}
69+
var flags LoginFlags
70+
71+
func init() {
72+
mcpPublisherCmd.AddCommand(loginCmd)
73+
loginCmd.Flags().StringVar(&flags.RegistryURL, "registry", DefaultRegistryURL, "Registry URL")
74+
loginCmd.Flags().StringVarP(&flags.Token, "token", "t", "", "GitHub Personal Access Token")
75+
loginCmd.Flags().StringVarP(&flags.Domain, "domain", "d", "", "Domain name")
76+
loginCmd.Flags().StringVarP(&flags.KvVault, "vault", "v", "", "The name of the Azure Key Vault resource")
77+
loginCmd.Flags().StringVarP(&flags.KvKeyName, "key", "k", "", "Name of the signing key in the Azure Key Vault")
78+
loginCmd.Flags().StringVarP(&flags.KmsResource, "resource", "r", "", "Google Cloud KMS resource name (e.g. projects/lotr/locations/global/keyRings/fellowship/cryptoKeys/frodo/cryptoKeyVersions/1)")
79+
loginCmd.Flags().StringVarP(&flags.PrivateKey, "private-key", "p", "", "Private key (hex)")
80+
loginCmd.Flags().VarP(&flags.CryptoAlgorithm, "algorithm", "a", "Cryptographic algorithm (ed25519, ecdsap384)")
81+
}
8082

81-
if method == "dns" || method == "http" {
82-
loginFlags.StringVar(&flags.Domain, "domain", "", "Domain name")
83-
if len(args) > 1 {
84-
switch args[1] {
85-
case string(AzureKeyVaultSignerType):
86-
flags.SignerType = AzureKeyVaultSignerType
87-
loginFlags.StringVar(&flags.KvVault, "vault", "", "The name of the Azure Key Vault resource")
88-
loginFlags.StringVar(&flags.KvKeyName, "key", "", "Name of the signing key in the Azure Key Vault")
89-
flags.ArgOffset = 2
90-
case string(GoogleKMSSignerType):
91-
flags.SignerType = GoogleKMSSignerType
92-
loginFlags.StringVar(&flags.KmsResource, "resource", "", "Google Cloud KMS resource name (e.g. projects/lotr/locations/global/keyRings/fellowship/cryptoKeys/frodo/cryptoKeyVersions/1)")
93-
flags.ArgOffset = 2
94-
}
83+
var loginCmd = &cobra.Command{
84+
Use: "login <method> [options]",
85+
Short: "Authenticate with the registry",
86+
Long: `Methods:
87+
github Interactive GitHub authentication
88+
github-oidc GitHub Actions OIDC authentication
89+
dns DNS-based authentication (requires --domain)
90+
http HTTP-based authentication (requires --domain)
91+
none Anonymous authentication (for testing)`,
92+
Args: func(_ *cobra.Command, args []string) error {
93+
if len(args) < 1 {
94+
return errors.New(`authentication method required
95+
96+
Usage: mcp-publisher login <method> [<signing provider>]
97+
98+
Methods:
99+
github Interactive GitHub authentication
100+
github-oidc GitHub Actions OIDC authentication
101+
dns DNS-based authentication (requires --domain)
102+
http HTTP-based authentication (requires --domain)
103+
none Anonymous authentication (for testing)
104+
105+
Signing providers:
106+
azure-key-vault Sign using Azure Key Vault
107+
google-kms Sign using Google Cloud KMS
108+
109+
The dns and http methods require a --private-key for in-process signing. For
110+
out-of-process signing, use one of the supported signing providers. Signing is
111+
needed for an authentication challenge with the registry.
112+
113+
The github and github-oidc methods do not support signing providers and
114+
authenticate using the GitHub as an identity provider.
115+
116+
Examples:
117+
# Interactive GitHub login, using device code flow
118+
mcp-publisher login github
119+
120+
# Sign in using a specific Ed25519 private key for DNS authentication
121+
mcp-publisher login dns -algorithm ed25519 -domain example.com -private-key <64 hex chars>
122+
123+
# Sign in using a specific ECDSA P-384 private key for DNS authentication
124+
mcp-publisher login dns -algorithm ecdsap384 -domain example.com -private-key <96 hex chars>
125+
126+
# Sign in with gcloud CLI, use Google Cloud KMS for signing in DNS authentication
127+
gcloud auth application-default login
128+
mcp-publisher login dns google-kms -domain example.com -resource projects/lotr/locations/global/keyRings/fellowship/cryptoKeys/frodo/cryptoKeyVersions/1
129+
130+
# Sign in with az CLI, use Azure Key Vault for signing in HTTP authentication
131+
az login
132+
mcp-publisher login http azure-key-vault -domain example.com -vault myvault -key mysigningkey`)
95133
}
96-
if flags.SignerType == NoSignerType {
97-
flags.SignerType = InProcessSignerType
98-
loginFlags.StringVar(&flags.PrivateKey, "private-key", "", "Private key (hex)")
99-
loginFlags.Var(&flags.CryptoAlgorithm, "algorithm", "Cryptographic algorithm (ed25519, ecdsap384)")
100-
}
101-
}
102-
err := loginFlags.Parse(args[flags.ArgOffset:])
103-
if err == nil {
104-
flags.RegistryURL = strings.TrimRight(flags.RegistryURL, "/")
105-
}
106-
107-
// Store the token in flags if it was provided
108-
if method == MethodGitHub {
109-
flags.Token = Token(token)
110-
}
111-
112-
return flags, err
134+
return nil
135+
},
136+
Example: `
137+
mcp-publisher login github
138+
mcp-publisher login dns --domain example.com --private-key <key>`,
139+
RunE: runLoginCommand,
113140
}
114141

115142
func createSigner(flags LoginFlags) (auth.Signer, error) {
@@ -127,10 +154,10 @@ func createSigner(flags LoginFlags) (auth.Signer, error) {
127154
}
128155
}
129156

130-
func createAuthProvider(method, registryURL, domain string, token Token, signer auth.Signer) (auth.Provider, error) {
157+
func createAuthProvider(method, registryURL, domain string, token string, signer auth.Signer) (auth.Provider, error) {
131158
switch method {
132159
case MethodGitHub:
133-
return auth.NewGitHubATProvider(true, registryURL, string(token)), nil
160+
return auth.NewGitHubATProvider(true, registryURL, token), nil
134161
case MethodGitHubOIDC:
135162
return auth.NewGitHubOIDCProvider(registryURL), nil
136163
case MethodDNS:
@@ -150,59 +177,26 @@ func createAuthProvider(method, registryURL, domain string, token Token, signer
150177
}
151178
}
152179

153-
func LoginCommand(args []string) error {
154-
if len(args) < 1 {
155-
return errors.New(`authentication method required
156-
157-
Usage: mcp-publisher login <method> [<signing provider>]
158-
159-
Methods:
160-
github Interactive GitHub authentication
161-
github-oidc GitHub Actions OIDC authentication
162-
dns DNS-based authentication (requires --domain)
163-
http HTTP-based authentication (requires --domain)
164-
none Anonymous authentication (for testing)
165-
166-
Signing providers:
167-
azure-key-vault Sign using Azure Key Vault
168-
google-kms Sign using Google Cloud KMS
169-
170-
The dns and http methods require a --private-key for in-process signing. For
171-
out-of-process signing, use one of the supported signing providers. Signing is
172-
needed for an authentication challenge with the registry.
173-
174-
The github and github-oidc methods do not support signing providers and
175-
authenticate using the GitHub as an identity provider.
176-
177-
Examples:
178-
179-
# Interactive GitHub login, using device code flow
180-
mcp-publisher login github
181-
182-
# Sign in using a specific Ed25519 private key for DNS authentication
183-
mcp-publisher login dns -algorithm ed25519 -domain example.com -private-key <64 hex chars>
184-
185-
# Sign in using a specific ECDSA P-384 private key for DNS authentication
186-
mcp-publisher login dns -algorithm ecdsap384 -domain example.com -private-key <96 hex chars>
187-
188-
# Sign in with gcloud CLI, use Google Cloud KMS for signing in DNS authentication
189-
gcloud auth application-default login
190-
mcp-publisher login dns google-kms -domain example.com -resource projects/lotr/locations/global/keyRings/fellowship/cryptoKeys/frodo/cryptoKeyVersions/1
191-
192-
# Sign in with az CLI, use Azure Key Vault for signing in HTTP authentication
193-
az login
194-
mcp-publisher login http azure-key-vault -domain example.com -vault myvault -key mysigningkey
195-
196-
`)
197-
}
198-
180+
var runLoginCommand = func(_ *cobra.Command, args []string) error {
181+
var (
182+
signer auth.Signer
183+
err error
184+
)
199185
method := args[0]
200-
flags, err := parseLoginFlags(method, args)
201-
if err != nil {
202-
return err
186+
flags.SignerType = NoSignerType
187+
if method == "http" || method == "dns" {
188+
if len(args) > 1 {
189+
switch args[1] {
190+
case string(AzureKeyVaultSignerType):
191+
flags.SignerType = AzureKeyVaultSignerType
192+
case string(GoogleKMSSignerType):
193+
flags.SignerType = GoogleKMSSignerType
194+
}
195+
} else {
196+
flags.SignerType = InProcessSignerType
197+
}
203198
}
204199

205-
var signer auth.Signer
206200
if flags.SignerType != NoSignerType {
207201
signer, err = createSigner(flags)
208202
if err != nil {

cmd/publisher/commands/logout.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,22 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7+
8+
"github.com/spf13/cobra"
79
)
810

9-
func LogoutCommand() error {
11+
func init() {
12+
mcpPublisherCmd.AddCommand(logoutCmd)
13+
}
14+
15+
var logoutCmd = &cobra.Command{
16+
Use: "logout",
17+
Short: "Clear saved authentication",
18+
Long: `This command removes the saved authentication token from your system.`,
19+
RunE: LogoutCommand,
20+
}
21+
22+
var LogoutCommand = func(_ *cobra.Command, _ []string) error {
1023
homeDir, err := os.UserHomeDir()
1124
if err != nil {
1225
return fmt.Errorf("failed to get home directory: %w", err)

cmd/publisher/commands/publish.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,24 @@ import (
1313
"strings"
1414

1515
apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
16+
"github.com/spf13/cobra"
1617
)
1718

18-
func PublishCommand(args []string) error {
19+
func init() {
20+
mcpPublisherCmd.AddCommand(publishCmd)
21+
}
22+
23+
var publishCmd = &cobra.Command{
24+
Use: "publish [server.json]",
25+
Short: "Publish server.json to the registry",
26+
Long: `Arguments:
27+
server.json Path to the server.json file (default: ./server.json)
28+
29+
You must be logged in before publishing. Run 'mcp-publisher login' first.`,
30+
RunE: RunPublishCommand,
31+
}
32+
33+
var RunPublishCommand = func(_ *cobra.Command, args []string) error {
1934
// Check for server.json file
2035
serverFile := "server.json"
2136
if len(args) > 0 && !strings.HasPrefix(args[0], "-") {

cmd/publisher/commands/publish_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func TestPublishCommand_Success(t *testing.T) {
3131
CreateTestServerJSON(t, serverJSON)
3232

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

3636
// Should succeed
3737
assert.NoError(t, err)
@@ -89,7 +89,7 @@ func TestPublishCommand_422ValidationFlow(t *testing.T) {
8989
CreateTestServerJSON(t, serverJSON)
9090

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

9494
// Should fail with validation error
9595
require.Error(t, err)
@@ -149,7 +149,7 @@ func TestPublishCommand_422WithMultipleIssues(t *testing.T) {
149149
}
150150
CreateTestServerJSON(t, serverJSON)
151151

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

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

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

170170
require.Error(t, err)
171171
assert.Contains(t, err.Error(), "not authenticated")
@@ -193,7 +193,7 @@ func TestPublishCommand_Non422Error(t *testing.T) {
193193
}
194194
CreateTestServerJSON(t, serverJSON)
195195

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

198198
require.Error(t, err)
199199
assert.Contains(t, err.Error(), "publish failed")
@@ -337,7 +337,7 @@ func TestPublishCommand_DeprecatedSchema(t *testing.T) {
337337
}
338338
CreateTestServerJSON(t, serverJSON)
339339

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

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

0 commit comments

Comments
 (0)