<!-- Provide a brief summary of your changes --> ## Motivation and Context This PR ensures that the draft URL points to the actual draft schema improvement suggested in https://github.com/modelcontextprotocol/registry/issues/814 ## How Has This Been Tested? <!-- Have you tested this in a real application? Which scenarios were tested? --> ## Breaking Changes <!-- Will users need to update their code or configurations? --> ## Types of changes <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist <!-- Go over all the following points, and put an `x` in all the boxes that apply. --> - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context <!-- Add any other context, implementation notes, or design decisions -->
108 lines
3.5 KiB
Go
108 lines
3.5 KiB
Go
package validators_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"regexp"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const serverSchemaPath = "../../docs/reference/server-json/draft/server.schema.json"
|
|
|
|
// schemaHelper provides utilities for extracting values from the JSON schema
|
|
type schemaHelper struct {
|
|
t *testing.T
|
|
schema map[string]interface{}
|
|
}
|
|
|
|
func loadSchema(t *testing.T) *schemaHelper {
|
|
t.Helper()
|
|
data, err := os.ReadFile(serverSchemaPath)
|
|
require.NoError(t, err, "Failed to read schema file")
|
|
|
|
var schema map[string]interface{}
|
|
err = json.Unmarshal(data, &schema)
|
|
require.NoError(t, err, "Failed to parse schema JSON")
|
|
|
|
return &schemaHelper{t: t, schema: schema}
|
|
}
|
|
|
|
// getDefinition returns a definition from the schema by name
|
|
func (s *schemaHelper) getDefinition(name string) map[string]interface{} {
|
|
s.t.Helper()
|
|
definitions := s.schema["definitions"].(map[string]interface{})
|
|
def, ok := definitions[name].(map[string]interface{})
|
|
require.True(s.t, ok, "Definition %q not found in schema", name)
|
|
return def
|
|
}
|
|
|
|
// getPropertyPattern extracts a regex pattern from a definition's property
|
|
func (s *schemaHelper) getPropertyPattern(definitionName, propertyName string) string {
|
|
s.t.Helper()
|
|
def := s.getDefinition(definitionName)
|
|
props := def["properties"].(map[string]interface{})
|
|
prop, ok := props[propertyName].(map[string]interface{})
|
|
require.True(s.t, ok, "Property %q not found in %s", propertyName, definitionName)
|
|
pattern, ok := prop["pattern"].(string)
|
|
require.True(s.t, ok, "Pattern not found for %s.%s", definitionName, propertyName)
|
|
return pattern
|
|
}
|
|
|
|
// TestTransportURLPattern validates the URL pattern used by StreamableHttpTransport and SseTransport.
|
|
// URLs must start with http://, https://, or a template variable like {baseUrl}.
|
|
func TestTransportURLPattern(t *testing.T) {
|
|
schema := loadSchema(t)
|
|
|
|
streamablePattern := schema.getPropertyPattern("StreamableHttpTransport", "url")
|
|
ssePattern := schema.getPropertyPattern("SseTransport", "url")
|
|
|
|
// Verify both transport types use the same pattern
|
|
assert.Equal(t, streamablePattern, ssePattern,
|
|
"StreamableHttpTransport and SseTransport should use identical URL patterns")
|
|
|
|
t.Logf("Pattern: %s", streamablePattern)
|
|
|
|
re, err := regexp.Compile(streamablePattern)
|
|
require.NoError(t, err, "Pattern should be valid regex")
|
|
|
|
// Test cases that SHOULD match
|
|
validCases := []string{
|
|
// Standard URLs
|
|
"https://api.example.com/mcp",
|
|
"http://localhost:8080/sse",
|
|
"https://example.com/path?query=value",
|
|
"https://api.example.com/v1/mcp",
|
|
// Template variables
|
|
"{baseUrl}",
|
|
"{baseUrl}/mcp",
|
|
"{server_url}/api/v1",
|
|
"{API_ENDPOINT}",
|
|
"{a}",
|
|
"{_private}/endpoint",
|
|
}
|
|
|
|
for _, tc := range validCases {
|
|
assert.True(t, re.MatchString(tc), "Expected %q to match pattern", tc)
|
|
}
|
|
|
|
// Test cases that should NOT match
|
|
invalidCases := []string{
|
|
"ftp://example.com", // wrong protocol
|
|
"example.com", // missing protocol or variable
|
|
"/relative/path", // relative path
|
|
"{invalid-name}/path", // hyphen in variable name
|
|
"{123invalid}", // variable starts with number
|
|
"", // empty string
|
|
"mailto:test@example.com", // wrong protocol
|
|
"file:///path/to/file", // wrong protocol
|
|
"{}/empty", // empty variable name
|
|
"{{nested}}/path", // nested braces
|
|
}
|
|
|
|
for _, tc := range invalidCases {
|
|
assert.False(t, re.MatchString(tc), "Expected %q to NOT match pattern", tc)
|
|
}
|
|
}
|