Implement custom server schema validation (MCP Spec 4.1.4 compliance)#1312
Implement custom server schema validation (MCP Spec 4.1.4 compliance)#1312
Conversation
- Add validateAgainstCustomSchema function that fetches and validates against custom schemas - Add AdditionalProperties field to StdinServerConfig to preserve custom fields - Implement UnmarshalJSON to capture additional properties for custom server types - Add test case for invalid custom config (missing required fields) - Fix test ID numbering: T-CFG-009→T-CFG-010, T-CFG-010→T-CFG-011, etc. - All compliance tests passing (T-CFG-010 through T-CFG-014) Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR implements custom server schema validation to achieve MCP Gateway Specification Section 4.1.4 compliance. Previously, the gateway acknowledged custom schema URLs but did not fetch or validate against them, violating a MUST requirement. The implementation adds schema fetching, compilation, and validation functionality, along with support for preserving custom fields during JSON unmarshaling.
Changes:
- Implements
validateAgainstCustomSchema()function that fetches custom JSON schemas via HTTPS, compiles them as JSON Schema Draft 7, and validates server configurations against them - Adds
AdditionalPropertiesfield and customUnmarshalJSON()method toStdinServerConfigto preserve custom schema-defined fields during JSON unmarshaling - Adds negative test case for invalid custom configurations and renumbers test IDs to align with specification (T-CFG-009→T-CFG-010 through T-CFG-013→T-CFG-014)
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| internal/config/validation.go | Implements custom schema fetching, compilation, and validation logic; replaces TODO with full implementation of validateAgainstCustomSchema() |
| internal/config/config_stdin.go | Adds AdditionalProperties field with custom unmarshaling to capture non-standard fields for custom server type validation |
| internal/config/custom_types_test.go | Adds test for invalid custom config validation and renumbers test IDs (T-CFG-010 through T-CFG-014) to match specification |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func validateAgainstCustomSchema(name string, server *StdinServerConfig, schemaURL string, jsonPath string) error { | ||
| logValidation.Printf("Fetching custom schema for validation: name=%s, url=%s", name, schemaURL) | ||
|
|
||
| // Fetch the custom schema using the existing helper | ||
| schemaJSON, err := fetchAndFixSchema(schemaURL) | ||
| if err != nil { | ||
| logValidation.Printf("Failed to fetch custom schema: name=%s, url=%s, error=%v", name, schemaURL, err) | ||
| return &rules.ValidationError{ | ||
| Field: "type", | ||
| Message: fmt.Sprintf("failed to fetch custom schema for server type '%s': %v", server.Type, err), | ||
| JSONPath: jsonPath, | ||
| Suggestion: fmt.Sprintf("Ensure the schema URL '%s' is accessible and returns a valid JSON Schema", schemaURL), | ||
| } | ||
| } | ||
|
|
||
| logValidation.Printf("Custom schema fetched successfully: name=%s, size=%d bytes", name, len(schemaJSON)) | ||
|
|
||
| // Parse the schema to extract its $id | ||
| var schemaObj map[string]interface{} | ||
| if err := json.Unmarshal(schemaJSON, &schemaObj); err != nil { | ||
| return &rules.ValidationError{ | ||
| Field: "type", | ||
| Message: fmt.Sprintf("failed to parse custom schema for server type '%s': %v", server.Type, err), | ||
| JSONPath: jsonPath, | ||
| Suggestion: fmt.Sprintf("The schema at '%s' must be valid JSON", schemaURL), | ||
| } | ||
| } | ||
|
|
||
| schemaID, ok := schemaObj["$id"].(string) | ||
| if !ok || schemaID == "" { | ||
| schemaID = schemaURL | ||
| } | ||
|
|
||
| // Compile the custom schema | ||
| compiler := jsonschema.NewCompiler() | ||
| compiler.Draft = jsonschema.Draft7 | ||
|
|
||
| // Add the schema with both URLs (the fetch URL and the $id URL) | ||
| if err := compiler.AddResource(schemaURL, strings.NewReader(string(schemaJSON))); err != nil { | ||
| return &rules.ValidationError{ | ||
| Field: "type", | ||
| Message: fmt.Sprintf("failed to compile custom schema for server type '%s': %v", server.Type, err), | ||
| JSONPath: jsonPath, | ||
| Suggestion: fmt.Sprintf("The schema at '%s' must be a valid JSON Schema Draft 7 document", schemaURL), | ||
| } | ||
| } | ||
| if schemaID != schemaURL { | ||
| if err := compiler.AddResource(schemaID, strings.NewReader(string(schemaJSON))); err != nil { | ||
| return &rules.ValidationError{ | ||
| Field: "type", | ||
| Message: fmt.Sprintf("failed to compile custom schema with $id for server type '%s': %v", server.Type, err), | ||
| JSONPath: jsonPath, | ||
| Suggestion: fmt.Sprintf("Check the $id field in the schema at '%s'", schemaURL), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| schema, err := compiler.Compile(schemaID) | ||
| if err != nil { | ||
| return &rules.ValidationError{ | ||
| Field: "type", | ||
| Message: fmt.Sprintf("failed to compile custom schema for server type '%s': %v", server.Type, err), | ||
| JSONPath: jsonPath, | ||
| Suggestion: fmt.Sprintf("The schema at '%s' must be a valid JSON Schema Draft 7 document", schemaURL), | ||
| } | ||
| } |
There was a problem hiding this comment.
The custom schema validation implementation fetches and compiles schemas on every validation call without caching. This differs from the main schema validation which uses sync.Once for efficient caching. If a configuration has multiple servers using the same custom type, each server will trigger a separate HTTP fetch and schema compilation, potentially impacting performance and causing unnecessary network traffic.
Consider implementing a caching mechanism similar to getOrCompileSchema in validation_schema.go using sync.Once or a map with sync.Mutex to cache compiled custom schemas by URL. This would be especially beneficial for configurations with multiple servers of the same custom type.
| })) | ||
| defer mockSchemaServer.Close() | ||
|
|
||
| // Invalid configuration that MISSING requiredField |
There was a problem hiding this comment.
Minor grammar issue in the comment. The word "MISSING" is unnecessarily capitalized and the phrasing could be improved. Consider: "Invalid configuration that is missing requiredField" or "Invalid configuration missing requiredField"
| // Invalid configuration that MISSING requiredField | |
| // Invalid configuration missing requiredField |
The gateway was acknowledging custom schema URLs but not fetching or validating against them, violating MCP Gateway Specification Section 4.1.4 MUST requirement: "If registered with an HTTPS URL, the gateway MUST fetch and apply the corresponding JSON Schema for validation."
Changes
Schema validation implementation (
internal/config/validation.go)validateAgainstCustomSchema()that fetches custom schemas via HTTPS, compiles them as JSON Schema Draft 7, and validates server configurationsfetchAndFixSchema()helper for schema retrieval with Draft 7 compatibility fixesCustom fields preservation (
internal/config/config_stdin.go)AdditionalPropertiesfield toStdinServerConfigto capture fields not in standard schemaUnmarshalJSON()to preserve custom schema-defined fields during JSON unmarshalingTest coverage (
internal/config/custom_types_test.go)invalid_custom_configtest case verifying validation fails when required custom fields are missingExample
A custom server type with required fields now properly validates:
{ "customSchemas": { "safeinputs": "https://example.com/safeinputs-schema.json" }, "mcpServers": { "my-server": { "type": "safeinputs", "requiredField": "value", "container": "ghcr.io/example/safeinputs:latest" } } }If
requiredFieldis missing and the schema requires it, validation now fails with a clear error message pointing to the schema URL.Compliance Status
✅ T-CFG-010: Valid custom type with registered schema
✅ T-CFG-011: Reject unregistered custom type
✅ T-CFG-012: Validate against custom schema (newly implemented)
✅ T-CFG-013: Reject reserved type names
✅ T-CFG-014: Custom schema fetch and cache
Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
example.com/tmp/go-build3647879484/b275/launcher.test /tmp/go-build3647879484/b275/launcher.test -test.testlogfile=/tmp/go-build3647879484/b275/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true --glob !.git bin/git R) + 1) if (dirname --glob !.hg git chec�� claude/implement-custom-server-schema-validation 500 .12/x64/bin/git fetchAndFixSchembase64 /home/REDACTED/wor-d x_amd64/cgo git(dns block)invalid-host-that-does-not-exist-12345.com/tmp/go-build3647879484/b260/config.test /tmp/go-build3647879484/b260/config.test -test.testlogfile=/tmp/go-build3647879484/b260/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true -bool -buildtags ache/Python/3.12.12/x64/bin/git -errorsas -ifaceassert -nilfunc git rev-�� --abbrev-ref HEAD k/_temp/ghcca-node/node/bin/git go ternal/fips140/e-d 64/pkg/tool/linu-o git(dns block)nonexistent.local/tmp/go-build3647879484/b275/launcher.test /tmp/go-build3647879484/b275/launcher.test -test.testlogfile=/tmp/go-build3647879484/b275/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true --glob !.git bin/git R) + 1) if (dirname --glob !.hg git chec�� claude/implement-custom-server-schema-validation 500 .12/x64/bin/git fetchAndFixSchembase64 /home/REDACTED/wor-d x_amd64/cgo git(dns block)slow.example.com/tmp/go-build3647879484/b275/launcher.test /tmp/go-build3647879484/b275/launcher.test -test.testlogfile=/tmp/go-build3647879484/b275/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true --glob !.git bin/git R) + 1) if (dirname --glob !.hg git chec�� claude/implement-custom-server-schema-validation 500 .12/x64/bin/git fetchAndFixSchembase64 /home/REDACTED/wor-d x_amd64/cgo git(dns block)this-host-does-not-exist-12345.com/tmp/go-build3647879484/b284/mcp.test /tmp/go-build3647879484/b284/mcp.test -test.testlogfile=/tmp/go-build3647879484/b284/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true --abbrev-ref HEAD it go zyR10eQBb .12/x64/bin/as s#^v#node/v#; \#/home/REDACTED/.nvm/test rev-�� --abbrev-ref HEAD /home/REDACTED/.cargo/bin/git 64/src/runtime/cbash w52HAkEye ache/go/1.25.6/x--version ; \#system# !d; HEAD(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
This section details on the original issue you should resolve
<issue_title>[compliance] Compliance Gap: Custom Server Schema Validation Not Implemented (MUST Violation)</issue_title>
<issue_description>## MCP Gateway Compliance Review - 2026-02-22
Summary
Found 1 critical compliance issue during daily review of the current codebase (commit
a6346a9). The custom server type schema validation is incomplete - the gateway acknowledges custom schema URLs but does not fetch or apply them for validation, violating a MUST requirement in the specification.Recent Changes Reviewed
a6346a9— Copilot/compile language support tester smoke workflows (Copilot/compile language support tester smoke workflows #1265)internal/config/,internal/server/,internal/launcher/Critical Issues (MUST violations)
1. Custom Server Schema Validation Not Implemented
Specification Section: 4.1.4 Custom Server Types
Deep Link: https://github.com/github/gh-aw/blob/main/docs/src/content/docs/reference/mcp-gateway.md#414-custom-server-types
Requirement (Spec v1.8.0, Section 4.1.4):
This covers the compliance test T-CFG-012: "Validate custom configuration against registered schema."
Current State:
In
internal/config/validation.go:194–200, thevalidateCustomServerConfigfunction explicitly skips schema validation with a TODO comment:The function returns
nil(success) without fetching the schema URL or validating the server configuration against it.Gap:
Any custom server configuration with any fields passes validation regardless of what its registered JSON Schema requires. For example, a custom type registered with a schema that requires a
requiredFieldfield will happily accept configs that omit that field entirely.Severity: 🔴 Critical (MUST violation)
File References:
internal/config/validation.go:186–200—validateCustomServerConfigfunctioninternal/config/custom_types_test.go:105–183—TestTCFG011_ValidateAgainstCustomSchema(test passes trivially because validation is never applied)2. Test ID Numbering Misalignment for Custom Schema Tests
Specification Section: 10.1.1 Configuration Tests
Deep Link: https://github.com/github/gh-aw/blob/main/docs/src/content/docs/reference/mcp-gateway.md#1011-configuration-tests
Current State:
The spec defines custom schema compliance tests starting at T-CFG-010, but the test file
internal/config/custom_types_test.golabels them starting at T-CFG-009:In the spec, T-CFG-009 is "Port range validation" (a standard test), but the code reuses T-CFG-009 for the first custom schema test.
Severity:⚠️ Minor (Test labeling/documentation inconsistency)
File References:
internal/config/custom_types_test.go:16,76,105,186,227— Test ID commentsCompliance Status
Suggested Remediation Tasks
Task 1: Implement Custom Schema Validation
Description: Complete the
validateCustomServerConfigfunction to actually fetch and validate server configurations against their registered custom schemas.Files:
internal/config/validation.go:186–200,internal/config/validation_schema.goSpecification Reference: https://github.com/github/gh-aw/blob/main/docs/src/conte...