Skip to content

Commit 4335162

Browse files
yuna0x0tadasant
authored andcommitted
docs: update download link of mcp-publisher in GitHub Actions to 1.1.0 (#565)
This pull request makes a minor update to the documentation for installing the MCP Publisher in GitHub Actions workflows. The change updates the installation command to use version 1.1.0 of the MCP Publisher instead of version 1.0.0. The `mcp-publisher` version in GitHub Actions has to be updated so it can publish the MCP server to the newest version of MCP Registry. Documentation update only. No test needed. No - [ ] 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) - [x] Documentation update <!-- Go over all the following points, and put an `x` in all the boxes that apply. --> - [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 - [ ] I have added appropriate error handling - [x] I have added or updated documentation as needed None
1 parent 69f45c4 commit 4335162

File tree

5 files changed

+32510
-8
lines changed

5 files changed

+32510
-8
lines changed

docs/reference/server-json/generic-server-json.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,3 +672,116 @@ For MCP servers that follow a custom installation path or are embedded in applic
672672
}
673673
```
674674

675+
676+
### Remote Server with URL Templating
677+
678+
This example demonstrates URL templating for remote servers, useful for multi-tenant deployments where each instance has its own endpoint. Unlike Package transports (which reference parent arguments/environment variables), Remote transports define their own variables:
679+
680+
```json
681+
{
682+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
683+
"name": "io.modelcontextprotocol.anonymous/multi-tenant-server",
684+
"description": "MCP server with configurable remote endpoint",
685+
"title": "Multi-Tenant Server",
686+
"version": "1.0.0",
687+
"remotes": [
688+
{
689+
"type": "streamable-http",
690+
"url": "https://anonymous.modelcontextprotocol.io/mcp/{tenant_id}",
691+
"variables": {
692+
"tenant_id": {
693+
"description": "Tenant identifier (e.g., 'us-cell1', 'emea-cell1')",
694+
"isRequired": true
695+
}
696+
}
697+
}
698+
]
699+
}
700+
```
701+
702+
Clients configure the tenant identifier, and the `{tenant_id}` variable in the URL gets replaced with the provided variable value to connect to the appropriate tenant endpoint (e.g., `https://anonymous.modelcontextprotocol.io/mcp/us-cell1` or `https://anonymous.modelcontextprotocol.io/mcp/emea-cell1`).
703+
704+
### Local Server with URL Templating
705+
706+
This example demonstrates URL templating for local/package servers, where variables reference parent Package arguments or environment variables:
707+
708+
```json
709+
{
710+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
711+
"name": "io.github.example/configurable-server",
712+
"description": "Local MCP server with configurable port",
713+
"title": "Configurable Server",
714+
"version": "1.0.0",
715+
"packages": [
716+
{
717+
"registryType": "npm",
718+
"registryBaseUrl": "https://registry.npmjs.org",
719+
"identifier": "@example/mcp-server",
720+
"version": "1.0.0",
721+
"transport": {
722+
"type": "streamable-http",
723+
"url": "http://localhost:{port}/mcp"
724+
},
725+
"packageArguments": [
726+
{
727+
"type": "named",
728+
"name": "--port",
729+
"description": "Port for the server to listen on",
730+
"default": "3000",
731+
"valueHint": "port"
732+
}
733+
]
734+
}
735+
]
736+
}
737+
```
738+
739+
The `{port}` variable in the URL references either the `--port` argument name or the `port` valueHint from packageArguments. When the package runs with `--port 8080`, the URL becomes `http://localhost:8080/mcp`.
740+
741+
### Deprecated Server Example
742+
743+
```json
744+
{
745+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
746+
"name": "io.github.example/old-weather",
747+
"description": "Legacy weather server - DEPRECATED: Use weather-v2 instead for new projects",
748+
"title": "Old Weather (Deprecated)",
749+
"repository": {
750+
"url": "https://github.com/example/old-weather",
751+
"source": "github",
752+
"id": "legacy-abc123-def456-789012-345678-901234567890"
753+
},
754+
"version": "0.9.5",
755+
"packages": [
756+
{
757+
"registryType": "npm",
758+
"registryBaseUrl": "https://registry.npmjs.org",
759+
"identifier": "@legacy/old-weather-server",
760+
"version": "0.9.5",
761+
"transport": {
762+
"type": "stdio"
763+
},
764+
"environmentVariables": [
765+
{
766+
"name": "WEATHER_API_KEY",
767+
"description": "Weather API key",
768+
"isRequired": true,
769+
"isSecret": true
770+
}
771+
]
772+
}
773+
],
774+
"_meta": {
775+
"io.modelcontextprotocol.registry/publisher-provided": {
776+
"tool": "legacy-publisher",
777+
"version": "0.8.1",
778+
"build_info": {
779+
"timestamp": "2023-06-15T09:30:00Z",
780+
"deprecation_notice": "This publisher is deprecated. Use npm-publisher v2.0+ for new projects.",
781+
"maintenance_mode": true,
782+
"final_version": true
783+
}
784+
}
785+
}
786+
}
787+
```

internal/validators/validators.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,20 @@ func collectAvailableVariables(pkg *model.Package) []string {
374374
return variables
375375
}
376376

377+
// collectRemoteTransportVariables extracts available variable names from a remote transport
378+
func collectRemoteTransportVariables(transport *model.Transport) []string {
379+
var variables []string
380+
381+
// Add variable names from the Variables map
382+
for variableName := range transport.Variables {
383+
if variableName != "" {
384+
variables = append(variables, variableName)
385+
}
386+
}
387+
388+
return variables
389+
}
390+
377391
// validatePackageTransport validates a package's transport with templating support
378392
func validatePackageTransport(transport *model.Transport, availableVariables []string) error {
379393
// Validate transport type is supported
@@ -405,7 +419,7 @@ func validatePackageTransport(transport *model.Transport, availableVariables []s
405419
}
406420
}
407421

408-
// validateRemoteTransport validates a remote transport (no templating allowed)
422+
// validateRemoteTransport validates a remote transport with optional templating
409423
func validateRemoteTransport(obj *model.Transport) error {
410424
// Validate transport type is supported - remotes only support streamable-http and sse
411425
switch obj.Type {
@@ -414,7 +428,22 @@ func validateRemoteTransport(obj *model.Transport) error {
414428
if obj.URL == "" {
415429
return fmt.Errorf("url is required for %s transport type", obj.Type)
416430
}
417-
// Validate URL format (no templates allowed for remotes, no localhost)
431+
432+
// Collect available variables from the transport's Variables field
433+
availableVariables := collectRemoteTransportVariables(obj)
434+
435+
// Validate URL format with template variable support
436+
if !IsValidTemplatedURL(obj.URL, availableVariables, true) { // true = allow templates for remotes
437+
// Check if it's a template variable issue or basic URL issue
438+
templateVars := extractTemplateVariables(obj.URL)
439+
if len(templateVars) > 0 {
440+
return fmt.Errorf("%w: template variables in URL %s reference undefined variables. Available variables: %v",
441+
ErrInvalidRemoteURL, obj.URL, availableVariables)
442+
}
443+
return fmt.Errorf("%w: %s", ErrInvalidRemoteURL, obj.URL)
444+
}
445+
446+
// Additional check: reject localhost URLs for remotes (like the old IsValidRemoteURL did)
418447
if !IsValidRemoteURL(obj.URL) {
419448
return fmt.Errorf("%w: %s", ErrInvalidRemoteURL, obj.URL)
420449
}
@@ -540,8 +569,11 @@ func validateWebsiteURLNamespaceMatch(serverJSON apiv0.ServerJSON) error {
540569

541570
// validateRemoteURLMatchesNamespace checks if a remote URL's hostname matches the publisher domain from the namespace
542571
func validateRemoteURLMatchesNamespace(remoteURL, namespace string) error {
572+
// Replace template variables with placeholders before parsing
573+
testURL := replaceTemplateVariables(remoteURL)
574+
543575
// Parse the URL to extract the hostname
544-
parsedURL, err := url.Parse(remoteURL)
576+
parsedURL, err := url.Parse(testURL)
545577
if err != nil {
546578
return fmt.Errorf("invalid URL format: %w", err)
547579
}

pkg/model/types.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,20 @@ const (
99
StatusDeleted Status = "deleted"
1010
)
1111

12-
// Transport represents transport configuration with optional URL templating
12+
// Transport represents transport configuration for Package context
1313
type Transport struct {
14-
Type string `json:"type"`
15-
URL string `json:"url,omitempty"`
16-
Headers []KeyValueInput `json:"headers,omitempty"`
14+
Type string `json:"type"`
15+
URL string `json:"url,omitempty"`
16+
Headers []KeyValueInput `json:"headers,omitempty"`
17+
Variables map[string]Input `json:"variables,omitempty"`
18+
}
19+
20+
// RemoteTransport represents transport configuration for Remote context with variables support
21+
type RemoteTransport struct {
22+
Type string `json:"type"`
23+
URL string `json:"url,omitempty"`
24+
Headers []KeyValueInput `json:"headers,omitempty"`
25+
Variables map[string]Input `json:"variables,omitempty"`
1726
}
1827

1928
// Package represents a package configuration.

0 commit comments

Comments
 (0)