Visitar URL original
fix(validators): reject incomplete repository metadata (#1555) · modelcontextprotocol/registry@efee8f0 · GitHub
Skip to content

Commit efee8f0

Browse files
authored
fix(validators): reject incomplete repository metadata (#1555)
Fixes #1546 ## What changed - preserve the existing behavior for an omitted `repository` field - reject a present repository object when `url` or `source` is missing or empty - add focused validator and publish-handler regressions, including the HTTP 422 response ## Validation Published head: `be0c97339875ebf083a06bf1a5686f763024e84d`. On 2026-10-03, the unchanged patch merged cleanly into upstream `main` at `bf4e88cbe8d1a635c06144ccea1d24cb52fa6186` for a local integration check: - full `internal/validators` and `internal/api/handlers/v0` suites with `-race`: pass - repository regression cases, omitted/complete repository controls, and the full publish-endpoint table: pass - `go vet` for both affected packages: pass - `go build ./cmd/registry`: pass - changed-file formatting and diff checks: pass The published head's GitHub **Build, Lint, and Validate** and **Tests** checks also passed. Those results are from 2026-08-20, not a new current-main CI run. Full repository `make check` was not rerun locally. No public API shape is changed, and the patch remains limited to repository semantic validation. Broader schema enforcement and existing-data cleanup are outside this PR. Co-authored-by: samrusani <14844597+samrusani@users.noreply.github.com>
1 parent 097bdc4 commit efee8f0

3 files changed

Lines changed: 92 additions & 2 deletions

File tree

‎internal/api/handlers/v0/publish_test.go‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,25 @@ func TestPublishEndpoint(t *testing.T) {
102102
},
103103
expectedStatus: http.StatusOK,
104104
},
105+
{
106+
name: "reject publish with empty repository",
107+
requestBody: apiv0.ServerJSON{
108+
Schema: model.CurrentSchemaURL,
109+
Name: "example/test-server",
110+
Description: "A test server",
111+
Repository: &model.Repository{},
112+
Version: "1.0.0",
113+
},
114+
tokenClaims: &auth.JWTClaims{
115+
AuthMethod: auth.MethodNone,
116+
Permissions: []auth.Permission{
117+
{Action: auth.PermissionActionPublish, ResourcePattern: "example/*"},
118+
},
119+
},
120+
setupRegistryService: func(_ service.RegistryService) {},
121+
expectedStatus: http.StatusUnprocessableEntity,
122+
expectedError: "Failed to publish server, invalid schema: call /validate for details",
123+
},
105124
{
106125
name: "missing authorization header",
107126
requestBody: apiv0.ServerJSON{},

‎internal/validators/validators.go‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,32 @@ func ValidateServerJSON(serverJSON *apiv0.ServerJSON, opts ValidationOptions) *V
119119
func validateRepository(ctx *ValidationContext, obj *model.Repository) *ValidationResult {
120120
result := &ValidationResult{Valid: true, Issues: []ValidationIssue{}}
121121

122-
// Skip validation if repository is nil or empty (optional field)
123-
if obj == nil || (obj.URL == "" && obj.Source == "") {
122+
// The repository field is optional, but its required fields must be present
123+
// when the repository object itself is provided.
124+
if obj == nil {
125+
return result
126+
}
127+
if obj.URL == "" {
128+
issue := NewValidationIssue(
129+
ValidationIssueTypeSemantic,
130+
ctx.Field("url").String(),
131+
"repository url is required when repository is provided",
132+
ValidationIssueSeverityError,
133+
"repository-url-required",
134+
)
135+
result.AddIssue(issue)
136+
}
137+
if obj.Source == "" {
138+
issue := NewValidationIssue(
139+
ValidationIssueTypeSemantic,
140+
ctx.Field("source").String(),
141+
"repository source is required when repository is provided",
142+
ValidationIssueSeverityError,
143+
"repository-source-required",
144+
)
145+
result.AddIssue(issue)
146+
}
147+
if !result.Valid {
124148
return result
125149
}
126150

‎internal/validators/validators_test.go‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,16 @@ func TestValidate(t *testing.T) {
249249
},
250250
expectedError: validators.ErrMultipleSlashesInServerName.Error(),
251251
},
252+
{
253+
name: "server without repository",
254+
serverDetail: apiv0.ServerJSON{
255+
Schema: model.CurrentSchemaURL,
256+
Name: "com.example/test-server",
257+
Description: "A test server",
258+
Version: "1.0.0",
259+
},
260+
expectedError: "",
261+
},
252262
{
253263
name: "valid server detail with all fields",
254264
serverDetail: apiv0.ServerJSON{
@@ -281,6 +291,43 @@ func TestValidate(t *testing.T) {
281291
},
282292
expectedError: "",
283293
},
294+
{
295+
name: "server with empty repository",
296+
serverDetail: apiv0.ServerJSON{
297+
Schema: model.CurrentSchemaURL,
298+
Name: "com.example/test-server",
299+
Description: "A test server",
300+
Repository: &model.Repository{},
301+
Version: "1.0.0",
302+
},
303+
expectedError: "repository url is required when repository is provided",
304+
},
305+
{
306+
name: "server with repository missing URL",
307+
serverDetail: apiv0.ServerJSON{
308+
Schema: model.CurrentSchemaURL,
309+
Name: "com.example/test-server",
310+
Description: "A test server",
311+
Repository: &model.Repository{
312+
Source: "github",
313+
},
314+
Version: "1.0.0",
315+
},
316+
expectedError: "repository url is required when repository is provided",
317+
},
318+
{
319+
name: "server with repository missing source",
320+
serverDetail: apiv0.ServerJSON{
321+
Schema: model.CurrentSchemaURL,
322+
Name: "com.example/test-server",
323+
Description: "A test server",
324+
Repository: &model.Repository{
325+
URL: "https://github.com/owner/repo",
326+
},
327+
Version: "1.0.0",
328+
},
329+
expectedError: "repository source is required when repository is provided",
330+
},
284331
{
285332
name: "server with invalid repository source",
286333
serverDetail: apiv0.ServerJSON{

0 commit comments

Comments
 (0)