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
8 changes: 4 additions & 4 deletions internal/validators/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
72 changes: 72 additions & 0 deletions internal/validators/utils_test.go
Original file line number Diff line number Diff line change
@@ -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))
})
}
}
14 changes: 14 additions & 0 deletions internal/validators/validators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
Loading