Skip to content

Commit 2dde049

Browse files
author
Avish Porwal
committed
Refactor validation code
1 parent 8d7e471 commit 2dde049

File tree

9 files changed

+611
-21
lines changed

9 files changed

+611
-21
lines changed

docs/server-json/schema.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,16 @@
5252
"name": {
5353
"type": "string",
5454
"description": "Server name/identifier",
55-
"example": "io.modelcontextprotocol/filesystem"
55+
"example": "io.modelcontextprotocol/filesystem",
56+
"minLength": 1,
57+
"maxLength": 200
5658
},
5759
"description": {
5860
"type": "string",
5961
"description": "Human-readable description of the server's functionality",
60-
"example": "Node.js server implementing Model Context Protocol (MCP) for filesystem operations."
62+
"example": "Node.js server implementing Model Context Protocol (MCP) for filesystem operations.",
63+
"minLength": 1,
64+
"maxLength": 100
6165
},
6266
"status": {
6367
"type": "string",
@@ -94,7 +98,8 @@
9498
"version": {
9599
"type": "string",
96100
"description": "Package version",
97-
"example": "1.0.2"
101+
"example": "1.0.2",
102+
"minLength": 1
98103
},
99104
"runtime_hint": {
100105
"type": "string",

internal/api/handlers/v0/publish.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/modelcontextprotocol/registry/internal/config"
1212
"github.com/modelcontextprotocol/registry/internal/model"
1313
"github.com/modelcontextprotocol/registry/internal/service"
14+
"github.com/modelcontextprotocol/registry/internal/validators"
1415
)
1516

1617
// PublishServerInput represents the input for publishing a server
@@ -63,6 +64,12 @@ func RegisterPublishEndpoint(api huma.API, registry service.RegistryService, cfg
6364
// Get server details from request body
6465
serverDetail := publishRequest.Server
6566

67+
// Validate the server detail
68+
validator := validators.NewObjectValidator()
69+
if err := validator.Validate(&serverDetail); err != nil {
70+
return nil, huma.Error400BadRequest(err.Error())
71+
}
72+
6673
// Verify that the token's repository matches the server being published
6774
if !jwtManager.HasPermission(serverDetail.Name, auth.PermissionActionPublish, claims.Permissions) {
6875
return nil, huma.Error403Forbidden("You do not have permission to publish this server")

internal/api/handlers/v0/publish_integration_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ func TestPublishIntegration(t *testing.T) {
105105
Name: "test-mcp-server-no-auth",
106106
Description: "A test MCP server without authentication",
107107
Repository: model.Repository{
108-
URL: "https://example.com/test-mcp-server",
109-
Source: "example",
110-
ID: "test-mcp-server",
108+
URL: "https://github.com/example/test-server",
109+
Source: "github",
110+
ID: "example/test-server",
111111
},
112112
VersionDetail: model.VersionDetail{
113113
Version: "1.0.0",
@@ -194,6 +194,11 @@ func TestPublishIntegration(t *testing.T) {
194194
VersionDetail: model.VersionDetail{
195195
Version: "1.0.0",
196196
},
197+
Repository: model.Repository{
198+
URL: "https://github.com/example/test-server",
199+
Source: "github",
200+
ID: "example/test-server",
201+
},
197202
},
198203
}
199204

internal/api/handlers/v0/publish_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ func TestPublishEndpoint(t *testing.T) {
112112
Name: "example/test-server",
113113
Description: "A test server without auth",
114114
Repository: model.Repository{
115-
URL: "https://example.com/test-server",
116-
Source: "example",
115+
URL: "https://github.com/example/test-server",
116+
Source: "github",
117117
ID: "example/test-server",
118118
},
119119
VersionDetail: model.VersionDetail{
@@ -173,6 +173,11 @@ func TestPublishEndpoint(t *testing.T) {
173173
VersionDetail: model.VersionDetail{
174174
Version: "1.0.0",
175175
},
176+
Repository: model.Repository{
177+
URL: "https://github.com/example/test-server",
178+
Source: "github",
179+
ID: "example/test-server",
180+
},
176181
},
177182
},
178183
tokenClaims: &auth.JWTClaims{
@@ -194,6 +199,11 @@ func TestPublishEndpoint(t *testing.T) {
194199
VersionDetail: model.VersionDetail{
195200
Version: "1.0.0",
196201
},
202+
Repository: model.Repository{
203+
URL: "https://github.com/example/test-server",
204+
Source: "github",
205+
ID: "example/test-server",
206+
},
197207
},
198208
},
199209
tokenClaims: &auth.JWTClaims{

internal/model/model.go

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,13 @@ const (
3232
ServerStatusDeprecated ServerStatus = "deprecated"
3333
)
3434

35-
3635
// Repository represents a source code repository as defined in the spec
3736
type Repository struct {
3837
URL string `json:"url" bson:"url"`
3938
Source string `json:"source" bson:"source"`
4039
ID string `json:"id,omitempty" bson:"id,omitempty"`
4140
}
4241

43-
4442
// create an enum for Format
4543
type Format string
4644

@@ -99,7 +97,7 @@ type Package struct {
9997
// Remote represents a remote connection endpoint
10098
type Remote struct {
10199
TransportType string `json:"transport_type" bson:"transport_type"`
102-
URL string `json:"url" bson:"url"`
100+
URL string `json:"url" format:"uri" bson:"url"`
103101
Headers []KeyValueInput `json:"headers,omitempty" bson:"headers,omitempty"`
104102
}
105103

@@ -108,12 +106,11 @@ type VersionDetail struct {
108106
Version string `json:"version" bson:"version"`
109107
}
110108

111-
112-
// ServerDetail represents complete server information as defined in the MCP spec (pure, no registry metadata)
109+
// ServerDetail represents complete server information as defined in the MCP spec (pure, no registry metadata)
113110
type ServerDetail struct {
114-
Name string `json:"name" bson:"name"`
115-
Description string `json:"description" bson:"description"`
116-
Status ServerStatus `json:"status,omitempty" bson:"status,omitempty"`
111+
Name string `json:"name" minLength:"1" maxLength:"200" bson:"name"`
112+
Description string `json:"description" minLength:"1" maxLength:"200" bson:"description"`
113+
Status ServerStatus `json:"status,omitempty" minLength:"1" bson:"status,omitempty"`
117114
Repository Repository `json:"repository,omitempty" bson:"repository"`
118115
VersionDetail VersionDetail `json:"version_detail" bson:"version_detail"`
119116
Packages []Package `json:"packages,omitempty" bson:"packages,omitempty"`
@@ -131,8 +128,8 @@ type RegistryMetadata struct {
131128

132129
// ServerRecord represents the complete storage model that separates server.json from registry metadata
133130
type ServerRecord struct {
134-
ServerJSON ServerDetail `json:"server" bson:"server"` // Pure MCP server.json
135-
RegistryMetadata RegistryMetadata `json:"registry_metadata" bson:"registry_metadata"` // Registry-generated data
131+
ServerJSON ServerDetail `json:"server" bson:"server"` // Pure MCP server.json
132+
RegistryMetadata RegistryMetadata `json:"registry_metadata" bson:"registry_metadata"` // Registry-generated data
136133
PublisherExtensions map[string]interface{} `json:"publisher_extensions" bson:"publisher_extensions"` // x-publisher extensions
137134
}
138135

@@ -252,14 +249,14 @@ func (sr *ServerRecord) ToServerResponse() ServerResponse {
252249
response := ServerResponse{
253250
Server: sr.ServerJSON,
254251
}
255-
252+
256253
// Add registry metadata extension
257254
response.XIOModelContextProtocolRegistry = sr.RegistryMetadata.CreateRegistryExtensions()["x-io.modelcontextprotocol.registry"]
258-
255+
259256
// Add publisher extensions directly
260257
if len(sr.PublisherExtensions) > 0 {
261258
response.XPublisher = sr.PublisherExtensions
262259
}
263-
260+
264261
return response
265262
}

internal/validators/constants.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package validators
2+
3+
import "errors"
4+
5+
// Error messages for validation
6+
var (
7+
// Server validation errors
8+
ErrNameRequired = errors.New("name is required")
9+
ErrServerNameTooLong = errors.New("server name is too long")
10+
ErrVersionRequired = errors.New("version is required")
11+
ErrDescriptionTooLong = errors.New("description is too long")
12+
13+
// Repository validation errors
14+
ErrInvalidRepositorySource = errors.New("invalid repository source")
15+
ErrInvalidRepositoryURL = errors.New("invalid repository URL")
16+
17+
// Package validation errors
18+
ErrPackageNameTooLong = errors.New("package name is too long")
19+
ErrPackageNameHasSpaces = errors.New("package name cannot contain spaces")
20+
21+
// Remote validation errors
22+
ErrInvalidRemoteURL = errors.New("invalid remote URL")
23+
)
24+
25+
// Constants for validation limits
26+
const (
27+
MaxLengthForServerName = 255
28+
MaxLengthForDescription = 1000
29+
MaxLengthForPackageName = 255
30+
)
31+
32+
// RepositorySource represents valid repository sources
33+
type RepositorySource string
34+
35+
const (
36+
SourceGitHub RepositorySource = "github"
37+
SourceGitLab RepositorySource = "gitlab"
38+
)

internal/validators/utils.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package validators
2+
3+
import (
4+
"net/url"
5+
"regexp"
6+
"strings"
7+
)
8+
9+
var (
10+
// Regular expressions for validating repository URLs
11+
// These regex patterns ensure the URL is in the format of a valid GitHub or GitLab repository
12+
// For example: // - GitHub: https://github.com/user/repo
13+
githubURLRegex = regexp.MustCompile(`^https?://(www\.)?github\.com/[\w.-]+/[\w.-]+/?$`)
14+
gitlabURLRegex = regexp.MustCompile(`^https?://(www\.)?gitlab\.com/[\w.-]+/[\w.-]+/?$`)
15+
)
16+
17+
// IsValidRepositoryURL checks if the given URL is valid for the specified repository source
18+
func IsValidRepositoryURL(source RepositorySource, url string) bool {
19+
switch source {
20+
case SourceGitHub:
21+
return githubURLRegex.MatchString(url)
22+
case SourceGitLab:
23+
return gitlabURLRegex.MatchString(url)
24+
}
25+
return false
26+
}
27+
28+
// HasNoSpaces checks if a string contains no spaces
29+
func HasNoSpaces(s string) bool {
30+
return !strings.Contains(s, " ")
31+
}
32+
33+
// IsValidURL checks if a URL is in valid format
34+
func IsValidURL(rawURL string) bool {
35+
// Parse the URL
36+
u, err := url.Parse(rawURL)
37+
if err != nil {
38+
return false
39+
}
40+
41+
// Check if scheme is present (http or https)
42+
if u.Scheme != "http" && u.Scheme != "https" {
43+
return false
44+
}
45+
46+
if u.Host == "" {
47+
return false
48+
}
49+
return true
50+
}

0 commit comments

Comments
 (0)