From dc2afe16e9ff09fdc0b925491a470cb23853ad6f Mon Sep 17 00:00:00 2001 From: go165 <196723798+go165@users.noreply.github.com> Date: Mon, 15 Jun 2026 19:14:34 +0800 Subject: [PATCH] fix(validators): allow nested GitLab repository paths --- internal/validators/utils.go | 8 +-- internal/validators/utils_test.go | 72 ++++++++++++++++++++++++++ internal/validators/validators_test.go | 14 +++++ 3 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 internal/validators/utils_test.go diff --git a/internal/validators/utils.go b/internal/validators/utils.go index 035132db..b00e917e 100644 --- a/internal/validators/utils.go +++ b/internal/validators/utils.go @@ -7,11 +7,11 @@ import ( ) var ( - // Regular expressions for validating repository URLs - // These regex patterns ensure the URL is in the format of a valid GitHub or GitLab repository - // For example: // - GitHub: https://github.com/user/repo + // Regular expressions for validating repository URLs. + // GitHub repository URLs are owner/repo. GitLab also supports nested groups, + // so accept two or more non-empty path segments there. githubURLRegex = regexp.MustCompile(`^https?://(www\.)?github\.com/[\w.-]+/[\w.-]+/?$`) - gitlabURLRegex = regexp.MustCompile(`^https?://(www\.)?gitlab\.com/[\w.-]+/[\w.-]+/?$`) + gitlabURLRegex = regexp.MustCompile(`^https?://(www\.)?gitlab\.com/(?:[\w.-]+/)+[\w.-]+/?$`) ) // IsValidRepositoryURL checks if the given URL is valid for the specified repository source diff --git a/internal/validators/utils_test.go b/internal/validators/utils_test.go new file mode 100644 index 00000000..1926dd64 --- /dev/null +++ b/internal/validators/utils_test.go @@ -0,0 +1,72 @@ +package validators_test + +import ( + "testing" + + "github.com/modelcontextprotocol/registry/internal/validators" + "github.com/stretchr/testify/assert" +) + +func TestIsValidRepositoryURL(t *testing.T) { + tests := []struct { + name string + source validators.RepositorySource + url string + valid bool + }{ + { + name: "github owner repo", + source: validators.SourceGitHub, + url: "https://github.com/modelcontextprotocol/registry", + valid: true, + }, + { + name: "github rejects nested paths", + source: validators.SourceGitHub, + url: "https://github.com/modelcontextprotocol/platform/registry", + valid: false, + }, + { + name: "gitlab group repo", + source: validators.SourceGitLab, + url: "https://gitlab.com/myorg/my-mcp-server", + valid: true, + }, + { + name: "gitlab nested subgroup repo", + source: validators.SourceGitLab, + url: "https://gitlab.com/myorg/team/subgroup/my-mcp-server", + valid: true, + }, + { + name: "gitlab nested subgroup repo with www and trailing slash", + source: validators.SourceGitLab, + url: "https://www.gitlab.com/myorg/team/subgroup/my-mcp-server/", + valid: true, + }, + { + name: "gitlab rejects missing repo", + source: validators.SourceGitLab, + url: "https://gitlab.com/myorg", + valid: false, + }, + { + name: "gitlab rejects empty path segment", + source: validators.SourceGitLab, + url: "https://gitlab.com/myorg//my-mcp-server", + valid: false, + }, + { + name: "gitlab rejects query string", + source: validators.SourceGitLab, + url: "https://gitlab.com/myorg/team/my-mcp-server?tab=readme", + valid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.valid, validators.IsValidRepositoryURL(tt.source, tt.url)) + }) + } +} diff --git a/internal/validators/validators_test.go b/internal/validators/validators_test.go index c3e5585b..0e77cdb8 100644 --- a/internal/validators/validators_test.go +++ b/internal/validators/validators_test.go @@ -323,6 +323,20 @@ func TestValidate(t *testing.T) { }, expectedError: validators.ErrInvalidRepositoryURL.Error(), }, + { + name: "server with valid nested GitLab repository URL", + serverDetail: apiv0.ServerJSON{ + Schema: model.CurrentSchemaURL, + Name: "com.example/test-server", + Description: "A test server", + Repository: &model.Repository{ + URL: "https://gitlab.com/myorg/team/subgroup/my-mcp-server", + Source: "gitlab", + }, + Version: "1.0.0", + }, + expectedError: "", + }, { name: "server with valid repository subfolder", serverDetail: apiv0.ServerJSON{