Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 27 additions & 10 deletions docs/reference/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -315,10 +315,7 @@ components:
description: A hint to help clients determine the appropriate runtime for the package. This field should be provided when `runtimeArguments` are present.
examples: [npx, uvx, docker, dnx]
transport:
anyOf:
- $ref: '#/components/schemas/StdioTransport'
- $ref: '#/components/schemas/StreamableHttpTransport'
- $ref: '#/components/schemas/SseTransport'
$ref: '#/components/schemas/LocalTransport'
description: Transport protocol configuration for the package
runtimeArguments:
type: array
Expand Down Expand Up @@ -474,8 +471,9 @@ components:
example: "streamable-http"
url:
type: string
description: URL template for the streamable-http transport. Variables in {curly_braces} reference argument valueHints, argument names, or environment variable names. After variable substitution, this should produce a valid URI.
description: "URL template for the streamable-http transport. Variables in {curly_braces} are resolved based on context: In Package context, they reference argument valueHints, argument names, or environment variable names from the parent Package. In Remote context, they reference variables from the transport's 'variables' object. After variable substitution, this should produce a valid URI."
example: "https://api.example.com/mcp"
pattern: "^https?://[^\\s]+$"
headers:
type: array
description: HTTP headers to include
Expand All @@ -495,15 +493,36 @@ components:
example: "sse"
url:
type: string
format: uri
description: Server-Sent Events endpoint URL
description: "Server-Sent Events endpoint URL template. Variables in {curly_braces} are resolved based on context: In Package context, they reference argument valueHints, argument names, or environment variable names from the parent Package. In Remote context, they reference variables from the transport's 'variables' object. After variable substitution, this should produce a valid URI."
example: "https://mcp-fs.example.com/sse"
pattern: "^https?://[^\\s]+$"
headers:
type: array
description: HTTP headers to include
items:
$ref: '#/components/schemas/KeyValueInput'

LocalTransport:
anyOf:
- $ref: '#/components/schemas/StdioTransport'
- $ref: '#/components/schemas/StreamableHttpTransport'
- $ref: '#/components/schemas/SseTransport'
description: Transport protocol configuration for local/package context

RemoteTransport:
allOf:
- anyOf:
- $ref: '#/components/schemas/StreamableHttpTransport'
- $ref: '#/components/schemas/SseTransport'
- type: object
properties:
variables:
type: object
description: "Configuration variables that can be referenced in URL template {curly_braces}. The key is the variable name, and the value defines the variable properties."
additionalProperties:
$ref: '#/components/schemas/Input'
description: Transport protocol configuration for remote context - extends StreamableHttpTransport or SseTransport with variables

Icon:
type: object
description: An optionally-sized icon that can be displayed in a user interface.
Expand Down Expand Up @@ -592,9 +611,7 @@ components:
remotes:
type: array
items:
anyOf:
- $ref: '#/components/schemas/StreamableHttpTransport'
- $ref: '#/components/schemas/SseTransport'
$ref: '#/components/schemas/RemoteTransport'
_meta:
type: object
description: "Extension metadata using reverse DNS namespacing for vendor-specific data"
Expand Down
87 changes: 87 additions & 0 deletions docs/reference/server-json/generic-server-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -672,3 +672,90 @@ For MCP servers that follow a custom installation path or are embedded in applic
}
```


### Remote Server with URL Templating

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:

```json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
"name": "io.modelcontextprotocol.anonymous/multi-tenant-server",
"description": "MCP server with configurable remote endpoint",
"title": "Multi-Tenant Server",
"version": "1.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://anonymous.modelcontextprotocol.io/mcp/{tenant_id}",
"variables": {
"tenant_id": {
"description": "Tenant identifier (e.g., 'us-cell1', 'emea-cell1')",
"isRequired": true
}
}
}
]
}
```

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`).

The same URL templating works with SSE transport:

```json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
"name": "io.modelcontextprotocol.anonymous/events-server",
"description": "MCP server using SSE with tenant-specific endpoints",
"version": "1.0.0",
"remotes": [
{
"type": "sse",
"url": "https://events.anonymous.modelcontextprotocol.io/sse/{tenant_id}",
"variables": {
"tenant_id": {
"description": "Tenant identifier",
"isRequired": true
}
}
}
]
}
```

### Local Server with URL Templating

This example demonstrates URL templating for local/package servers, where variables reference parent Package arguments or environment variables:

```json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
"name": "io.github.example/configurable-server",
"description": "Local MCP server with configurable port",
"title": "Configurable Server",
"version": "1.0.0",
"packages": [
{
"registryType": "npm",
"registryBaseUrl": "https://registry.npmjs.org",
"identifier": "@example/mcp-server",
"version": "1.0.0",
"transport": {
"type": "streamable-http",
"url": "http://localhost:{--port}/mcp"
},
"packageArguments": [
{
"type": "named",
"name": "--port",
"description": "Port for the server to listen on",
"default": "3000"
}
]
}
]
}
```

The `{--port}` variable in the URL references the `--port` argument `name` from packageArguments. For positional arguments, an argument with the `valueHint` of `port` could similarly be referened as `{port}`. When the package runs with `--port 8080`, the URL becomes `http://localhost:8080/mcp`.
69 changes: 47 additions & 22 deletions docs/reference/server-json/server.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,20 @@
}
]
},
"LocalTransport": {
"anyOf": [
{
"$ref": "#/definitions/StdioTransport"
},
{
"$ref": "#/definitions/StreamableHttpTransport"
},
{
"$ref": "#/definitions/SseTransport"
}
],
"description": "Transport protocol configuration for local/package context"
},
"NamedArgument": {
"allOf": [
{
Expand Down Expand Up @@ -262,17 +276,7 @@
"type": "string"
},
"transport": {
"anyOf": [
{
"$ref": "#/definitions/StdioTransport"
},
{
"$ref": "#/definitions/StreamableHttpTransport"
},
{
"$ref": "#/definitions/SseTransport"
}
],
"$ref": "#/definitions/LocalTransport",
"description": "Transport protocol configuration for the package"
},
"version": {
Expand Down Expand Up @@ -337,6 +341,33 @@
],
"description": "A positional input is a value inserted verbatim into the command line."
},
"RemoteTransport": {
"allOf": [
{
"anyOf": [
{
"$ref": "#/definitions/StreamableHttpTransport"
},
{
"$ref": "#/definitions/SseTransport"
}
]
},
{
"properties": {
"variables": {
"additionalProperties": {
"$ref": "#/definitions/Input"
},
"description": "Configuration variables that can be referenced in URL template {curly_braces}. The key is the variable name, and the value defines the variable properties.",
"type": "object"
}
},
"type": "object"
}
],
"description": "Transport protocol configuration for remote context - extends StreamableHttpTransport or SseTransport with variables"
},
"Repository": {
"description": "Repository metadata for the MCP server source code. Enables users and security experts to inspect the code, improving transparency.",
"properties": {
Expand Down Expand Up @@ -427,14 +458,7 @@
},
"remotes": {
"items": {
"anyOf": [
{
"$ref": "#/definitions/StreamableHttpTransport"
},
{
"$ref": "#/definitions/SseTransport"
}
]
"$ref": "#/definitions/RemoteTransport"
},
"type": "array"
},
Expand Down Expand Up @@ -487,9 +511,9 @@
"type": "string"
},
"url": {
"description": "Server-Sent Events endpoint URL",
"description": "Server-Sent Events endpoint URL template. Variables in {curly_braces} are resolved based on context: In Package context, they reference argument valueHints, argument names, or environment variable names from the parent Package. In Remote context, they reference variables from the transport's 'variables' object. After variable substitution, this should produce a valid URI.",
"example": "https://mcp-fs.example.com/sse",
"format": "uri",
"pattern": "^https?://[^\\s]+$",
"type": "string"
}
},
Expand Down Expand Up @@ -533,8 +557,9 @@
"type": "string"
},
"url": {
"description": "URL template for the streamable-http transport. Variables in {curly_braces} reference argument valueHints, argument names, or environment variable names. After variable substitution, this should produce a valid URI.",
"description": "URL template for the streamable-http transport. Variables in {curly_braces} are resolved based on context: In Package context, they reference argument valueHints, argument names, or environment variable names from the parent Package. In Remote context, they reference variables from the transport's 'variables' object. After variable substitution, this should produce a valid URI.",
"example": "https://api.example.com/mcp",
"pattern": "^https?://[^\\s]+$",
"type": "string"
}
},
Expand Down
5 changes: 3 additions & 2 deletions internal/validators/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ var (
ErrReservedVersionString = errors.New("version string 'latest' is reserved and cannot be used")
ErrVersionLooksLikeRange = errors.New("version must be a specific version, not a range")

// Remote validation errors
ErrInvalidRemoteURL = errors.New("invalid remote URL")
// Transport validation errors
ErrInvalidPackageTransportURL = errors.New("invalid package transport URL")
ErrInvalidRemoteURL = errors.New("invalid remote URL")

// Registry validation errors
ErrUnsupportedRegistryBaseURL = errors.New("unsupported registry base URL")
Expand Down
22 changes: 13 additions & 9 deletions internal/validators/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,13 @@ func replaceTemplateVariables(rawURL string) string {
result = strings.ReplaceAll(result, placeholder, replacement)
}

// Handle any remaining {variable} patterns with generic placeholder
// Handle any remaining {variable} patterns with context-appropriate placeholders
// If the variable is in a port position (after a colon in the host), use a numeric placeholder
// Pattern: :/{variable} or :{variable}/ or :{variable} at end
portRe := regexp.MustCompile(`:(\{[^}]+\})(/|$)`)
result = portRe.ReplaceAllString(result, ":8080$2")

// Replace any other remaining {variable} patterns with generic placeholder
re := regexp.MustCompile(`\{[^}]+\}`)
result = re.ReplaceAllString(result, "placeholder")

Expand Down Expand Up @@ -132,8 +138,11 @@ func IsValidRemoteURL(rawURL string) bool {
return false
}

// Replace template variables with placeholders before parsing for localhost check
testURL := replaceTemplateVariables(rawURL)

// Parse the URL to check for localhost restriction
u, err := url.Parse(rawURL)
u, err := url.Parse(testURL)
if err != nil {
return false
}
Expand All @@ -149,8 +158,8 @@ func IsValidRemoteURL(rawURL string) bool {

// IsValidTemplatedURL validates a URL with template variables against available variables
// For packages: validates that template variables reference package arguments or environment variables
// For remotes: disallows template variables entirely
func IsValidTemplatedURL(rawURL string, availableVariables []string, allowTemplates bool) bool {
// For remotes: validates that template variables reference the transport's variables map
func IsValidTemplatedURL(rawURL string, availableVariables []string) bool {
// First check basic URL structure
if !IsValidURL(rawURL) {
return false
Expand All @@ -164,11 +173,6 @@ func IsValidTemplatedURL(rawURL string, availableVariables []string, allowTempla
return true
}

// If templates are not allowed (e.g., for remotes), reject URLs with templates
if !allowTemplates {
return false
}

// Validate that all template variables are available
availableSet := make(map[string]bool)
for _, v := range availableVariables {
Expand Down
Loading
Loading