Add optional title field to server.json schema (#608)

## Summary

Introduces a new optional `title` field for MCP servers to provide a
clean, human-readable display name that clients and subregistries can
use for display purposes.

Fixes #606

## Changes

- Added `title` field to `ServerJSON` type (optional, max 100 chars)
- Added validation to reject titles ending with 'MCP Server', 'MCP
server', or 'MCP' suffixes
- Updated JSON schema and OpenAPI specification
- Added comprehensive test coverage for title validation
- Updated all documentation examples to demonstrate the new field

## Validation

The title field:
- Is optional (can be omitted)
- Must not be only whitespace if provided
- Must not end with MCP-related suffixes (case-insensitive)
- Can contain 'MCP' in the middle (e.g., "MCP Weather API" is valid)

Examples:
- ✅ Valid: "GitHub", "Weather API", "Slack Integration"
- ❌ Invalid: "GitHub MCP", "Weather MCP Server", "Slack-MCP"

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
adam jones 2025-10-06 11:57:01 +01:00 • committed by GitHub
commit 44e35020b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1709,3 +1709,159 @@ func createValidServerWithArgument(arg model.Argument) apiv0.ServerJSON {
},
}
}
func TestValidateTitle(t *testing.T) {
tests := []struct {
name string
serverDetail apiv0.ServerJSON
expectedError string
}{
{
name: "Valid title without MCP suffix",
serverDetail: apiv0.ServerJSON{
Schema: model.CurrentSchemaURL,
Name: "com.example/test-server",
Description: "A test server",
Title: "GitHub",
Repository: model.Repository{
URL: "https://github.com/owner/repo",
Source: "github",
},
Version: "1.0.0",
},
expectedError: "",
},
{
name: "Valid title with multiple words",
serverDetail: apiv0.ServerJSON{
Schema: model.CurrentSchemaURL,
Name: "com.example/test-server",
Description: "A test server",
Title: "Weather API",
Repository: model.Repository{
URL: "https://github.com/owner/repo",
Source: "github",
},
Version: "1.0.0",
},
expectedError: "",
},
{
name: "Empty title is allowed (optional field)",
serverDetail: apiv0.ServerJSON{
Schema: model.CurrentSchemaURL,
Name: "com.example/test-server",
Description: "A test server",
Title: "",
Repository: model.Repository{
URL: "https://github.com/owner/repo",
Source: "github",
},
Version: "1.0.0",
},
expectedError: "",
},
{
name: "Allows title with 'MCP Server' suffix",
serverDetail: apiv0.ServerJSON{
Schema: model.CurrentSchemaURL,
Name: "com.example/test-server",
Description: "A test server",
Title: "GitHub MCP Server",
Repository: model.Repository{
URL: "https://github.com/owner/repo",
Source: "github",
},
Version: "1.0.0",
},
expectedError: "",
},
{
name: "Allows title with 'MCP' suffix",
serverDetail: apiv0.ServerJSON{
Schema: model.CurrentSchemaURL,
Name: "com.example/test-server",
Description: "A test server",
Title: "GitHub MCP",
Repository: model.Repository{
URL: "https://github.com/owner/repo",
Source: "github",
},
Version: "1.0.0",
},
expectedError: "",
},
{
name: "Allows title with 'mcp server' suffix (lowercase)",
serverDetail: apiv0.ServerJSON{
Schema: model.CurrentSchemaURL,
Name: "com.example/test-server",
Description: "A test server",
Title: "GitHub mcp server",
Repository: model.Repository{
URL: "https://github.com/owner/repo",
Source: "github",
},
Version: "1.0.0",
},
expectedError: "",
},
{
name: "Allows title with hyphenated 'MCP' suffix",
serverDetail: apiv0.ServerJSON{
Schema: model.CurrentSchemaURL,
Name: "com.example/test-server",
Description: "A test server",
Title: "GitHub-MCP",
Repository: model.Repository{
URL: "https://github.com/owner/repo",
Source: "github",
},
Version: "1.0.0",
},
expectedError: "",
},
{
name: "Allows 'MCP' in the middle of title",
serverDetail: apiv0.ServerJSON{
Schema: model.CurrentSchemaURL,
Name: "com.example/test-server",
Description: "A test server",
Title: "MCP Weather API",
Repository: model.Repository{
URL: "https://github.com/owner/repo",
Source: "github",
},
Version: "1.0.0",
},
expectedError: "",
},
{
name: "Rejects title with only whitespace",
serverDetail: apiv0.ServerJSON{
Schema: model.CurrentSchemaURL,
Name: "com.example/test-server",
Description: "A test server",
Title: " ",
Repository: model.Repository{
URL: "https://github.com/owner/repo",
Source: "github",
},
Version: "1.0.0",
},
expectedError: "title cannot be only whitespace",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validators.ValidateServerJSON(&tt.serverDetail)
if tt.expectedError == "" {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.expectedError)
}
})
}
}