package commands import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "os" "strings" apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0" ) func PublishCommand(args []string) error { // Check for server.json file serverFile := "server.json" if len(args) > 0 && !strings.HasPrefix(args[0], "-") { serverFile = args[0] } // Read server.json serverData, err := os.ReadFile(serverFile) if err != nil { if os.IsNotExist(err) { return fmt.Errorf("server.json not found. Run 'mcp-publisher init' to create one") } return fmt.Errorf("failed to read server.json: %w", err) } if err := validateJSONUnicode(serverFile, serverData); err != nil { return err } // Validate JSON var serverJSON apiv0.ServerJSON if err := json.Unmarshal(serverData, &serverJSON); err != nil { return fmt.Errorf("invalid server.json: %w", err) } // Load saved token tokenPath, err := tokenFilePath() if err != nil { return err } tokenData, err := os.ReadFile(tokenPath) if err != nil { if os.IsNotExist(err) { return notAuthenticatedError() } return fmt.Errorf("failed to read token: %w", err) } var tokenInfo map[string]string if err := json.Unmarshal(tokenData, &tokenInfo); err != nil { return fmt.Errorf("invalid token data: %w", err) } token := tokenInfo["token"] registryURL := tokenInfo["registry"] if registryURL == "" { registryURL = DefaultRegistryURL } // Publish to registry _, _ = fmt.Fprintf(os.Stdout, "Publishing to %s...\n", registryURL) response, statusCode, err := publishToRegistry(registryURL, serverData, token) if err != nil { // If publish failed with 422, call validate endpoint to show detailed errors if statusCode == http.StatusUnprocessableEntity { _, _ = fmt.Fprintln(os.Stdout, "Validation failed. Checking detailed validation errors...") _, _ = fmt.Fprintln(os.Stdout) // Call validate endpoint (same as validate command does) result, validateErr := validateViaAPI(registryURL, serverData) if validateErr != nil { // If validate also fails, return original publish error return fmt.Errorf("publish failed: %w", err) } // Print validation results using shared formatting logic formattedErrorMsg := printValidationIssues(result, &serverJSON) if !result.Valid { // Return error with formatted message if available if formattedErrorMsg != "" { return fmt.Errorf("%s", formattedErrorMsg) } return fmt.Errorf("validation failed") } } // For non-422 errors, return the original error return fmt.Errorf("publish failed: %w", err) } _, _ = fmt.Fprintln(os.Stdout, "✓ Successfully published") _, _ = fmt.Fprintf(os.Stdout, "✓ Server %s version %s\n", response.Server.Name, response.Server.Version) return nil } func publishToRegistry(registryURL string, serverData []byte, token string) (*apiv0.ServerResponse, int, error) { if err := validateJSONUnicode("server.json", serverData); err != nil { return nil, 0, err } // Parse the server JSON data var serverJSON apiv0.ServerJSON err := json.Unmarshal(serverData, &serverJSON) if err != nil { return nil, 0, fmt.Errorf("error parsing server.json file: %w", err) } // Convert to JSON jsonData, err := json.Marshal(serverJSON) if err != nil { return nil, 0, fmt.Errorf("error serializing request: %w", err) } // Ensure URL ends with the publish endpoint if !strings.HasSuffix(registryURL, "/") { registryURL += "/" } publishURL := registryURL + "v0/publish" // Create and send request req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, publishURL, bytes.NewBuffer(jsonData)) if err != nil { return nil, 0, fmt.Errorf("error creating request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(req) if err != nil { return nil, 0, fmt.Errorf("error sending request: %w", err) } defer resp.Body.Close() // Read response body, err := io.ReadAll(resp.Body) if err != nil { return nil, resp.StatusCode, fmt.Errorf("error reading response: %w", err) } if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK { return nil, resp.StatusCode, fmt.Errorf("server returned status %d: %s", resp.StatusCode, body) } var serverResponse apiv0.ServerResponse if err := json.Unmarshal(body, &serverResponse); err != nil { return nil, resp.StatusCode, err } return &serverResponse, resp.StatusCode, nil }