feat: Add website_url field for MCP servers (#422)

Adds optional website_url field to the server.json schema allowing
publishers to provide a url to the server's homepage, documentation, or
project website. This provides a central link for users to learn more
about the server. Particularly useful when the server has custom
installation instructions or setup requirements.

Issue: #183 
PR: #130 
Discussion: #274 

## Motivation and Context
This change addresses the need identified in #130, #183 and #274 for a
standardized way to direct users to an official landing page similar to
`homepage` used in NPM
([example](https://www.npmjs.com/package/@modelcontextprotocol/sdk)). It
is also serves as a viable option for MCP Servers that don't follow
typical package manager patterns (e.g., embedded in a Desktop app).

## How Has This Been Tested?
- Schema validation: All 13 example server.json files validate
successfully against the updated schema
- Go validator tests: Added additional test cases covering URL format
validation and namespace matching
- Run all checks: Updated example files to work with anonymous
publishing and verified `make check` passes

## Breaking Changes
None. The website_url field is optional and backward compatible with
existing server.json files.

## Types of changes
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update

## Checklist
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed

## Additional context
Security considerations:
- Website URL validation enforces that the domain matches the
publisher's reverse-DNS namespace
- HTTP/HTTPS protocols are required (no file:// or other schemes)
- Validation is consistent with existing remote URL validation patterns
This commit is contained in:
Kalle Bylin 2025-09-11 03:29:46 +02:00 • committed by GitHub
commit 17eb56d4c7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -27,7 +27,8 @@ func TestValidate(t *testing.T) {
Source: "github",
ID: "owner/repo",
},
Version: "1.0.0",
Version: "1.0.0",
WebsiteURL: "https://example.com/docs",
Packages: []model.Package{
{
Identifier: "test-package",
@ -170,6 +171,104 @@ func TestValidate(t *testing.T) {
},
expectedError: validators.ErrInvalidSubfolderPath.Error(),
},
{
name: "server with valid website URL",
serverDetail: apiv0.ServerJSON{
Name: "com.example/test-server",
Description: "A test server",
Repository: model.Repository{
URL: "https://github.com/owner/repo",
Source: "github",
},
Version: "1.0.0",
WebsiteURL: "https://example.com/docs",
},
expectedError: "",
},
{
name: "server with invalid website URL - no scheme",
serverDetail: apiv0.ServerJSON{
Name: "com.example/test-server",
Description: "A test server",
Repository: model.Repository{
URL: "https://github.com/owner/repo",
Source: "github",
},
Version: "1.0.0",
WebsiteURL: "example.com/docs",
},
expectedError: "website URL must be absolute (include scheme): example.com/docs",
},
{
name: "server with invalid website URL - invalid scheme",
serverDetail: apiv0.ServerJSON{
Name: "com.example/test-server",
Description: "A test server",
Repository: model.Repository{
URL: "https://github.com/owner/repo",
Source: "github",
},
Version: "1.0.0",
WebsiteURL: "ftp://example.com/docs",
},
expectedError: "website URL must use http or https scheme: ftp://example.com/docs",
},
{
name: "server with malformed website URL",
serverDetail: apiv0.ServerJSON{
Name: "com.example/test-server",
Description: "A test server",
Repository: model.Repository{
URL: "https://github.com/owner/repo",
Source: "github",
},
Version: "1.0.0",
WebsiteURL: "ht tp://example.com/docs",
},
expectedError: "invalid website URL:",
},
{
name: "server with website URL that matches namespace domain",
serverDetail: apiv0.ServerJSON{
Name: "com.example/test-server",
Description: "A test server",
Repository: model.Repository{
URL: "https://github.com/owner/repo",
Source: "github",
},
Version: "1.0.0",
WebsiteURL: "https://example.com/docs",
},
expectedError: "",
},
{
name: "server with website URL subdomain that matches namespace",
serverDetail: apiv0.ServerJSON{
Name: "com.example/test-server",
Description: "A test server",
Repository: model.Repository{
URL: "https://github.com/owner/repo",
Source: "github",
},
Version: "1.0.0",
WebsiteURL: "https://docs.example.com/mcp",
},
expectedError: "",
},
{
name: "server with website URL that does not match namespace",
serverDetail: apiv0.ServerJSON{
Name: "com.example/test-server",
Description: "A test server",
Repository: model.Repository{
URL: "https://github.com/owner/repo",
Source: "github",
},
Version: "1.0.0",
WebsiteURL: "https://different.com/docs",
},
expectedError: "website URL https://different.com/docs does not match namespace com.example/test-server",
},
{
name: "package with spaces in name",
serverDetail: apiv0.ServerJSON{