-
Notifications
You must be signed in to change notification settings - Fork 430
feat: Add LocalTransport/RemoteTransport with URL template variables #570
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
"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} reference variable names from the 'variables' object. If variables are not provided, {curly_braces} should be treated as literal text. After variable substitution, this should produce a valid URI.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea with the references initially here is that a server that is launched with stdio and has, for example, a --port
argument could automatically reference the value of that argument in the transport. I hypothesize the vast majority of locally-launched servers are going to have an address detereministic from their command line inputs and (from a client PoV) asking the user to fill in the port in the url
separately from a port in the package_arguments
is just confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also,
All existing server.json files remain valid
this is not the case -- this change is breaking for any packages that were already published with the original reference behavior (not sure if there are any, this was only in a recent addition)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, right. Thanks for the reminder that was the original reason we didn't include a separate field here.
The learning we've had since is that this doesn't work for the usage of StreamableHttpTransport
and SseTransport
within Remote
.
Will think more on how we can properly combine (or separate out) the use cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
definitely -- I suggest perhaps combining the variables
with an allOf
in just the Remote
case and adding some description in the property.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also re -
this is not the case -- this change is breaking for any packages that were already published with the original reference behavior (not sure if there are any, this was only in a recent addition)
That's my bad - I had forgotten this original use case inside Package
and thus thought the reference to arguments/envvars was just a typo. This is all clearer now that you've reminded me :) Thanks again
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feeling pretty good about the direction now: https://github.com/modelcontextprotocol/registry/pull/570/files
Not ready to land this, but intent for me here was to explore whether we can add this functionality without breaking changes. I think the answer is yes, so might punt this for a few days.
73c2f75
to
622a257
Compare
I somehow missed this PR and created #576 (which should get closed if this PR is adopted). FWIW, I've reviewed this PR and am a fan of the approach (including adding variables to the remote transports for URI substitution). |
I'm happy with this approach 👍. Might just need a rebase and minor code cleanup |
OK, I really like this PR and don't want to slow it down. That being said, in going through existing registry entries with an eye toward making them take full advantage of the configuration support offered, I ran across a case that might impact this. The general use case for this feature is configurable ports for the package transport url (so we configure the port we want the server to run on, and then have the transport config pick up that same port from the runtime config). But what about the case where the port is not an argument or env var, but is another part of the config? For example (from a published server): Current {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json",
"name": "io.github.SamYuan1990/i18n-agent-action",
"description": "An i18n github action for language translate",
"version": "mcp",
"packages": [
{
"registryType": "oci",
"registryBaseUrl": "https://ghcr.io",
"identifier": "SamYuan1990/i18n-agent-action",
"version": "mcp",
"runtimeHint": "docker",
"transport": {
"type": "sse",
"url": "https://example.com:8080/sse"
},
"runtimeArguments": [
{
"description": "Port mapping from host to container",
"value": "8080:8080",
"type": "named",
"name": "-p"
}
]
}
]
} If we wanted to parameterize the port so we can change it and have it be reflected in the docker command and the transport url, we would do something like this: Parameterized {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json",
"name": "io.github.SamYuan1990/i18n-agent-action",
"description": "An i18n github action for language translate",
"version": "mcp",
"packages": [
{
"registryType": "oci",
"registryBaseUrl": "https://ghcr.io",
"identifier": "SamYuan1990/i18n-agent-action",
"version": "mcp",
"runtimeHint": "docker",
"transport": {
"type": "sse",
"url": "https://example.com:{host_port}/sse"
},
"runtimeArguments": [
{
"description": "Port mapping from host to container",
"value": "{host_port}:8080",
"type": "named",
"name": "-p",
"variables": {
"host_port": {
"description": "The host (local) port",
"isRequired": true,
"format": "number",
"default": "8080"
}
}
}
]
}
]
} For docker we have to parameterize (templatize) the port (-p) argument with a variable to make the host port configurable. With the current resolution logic, there would be no way to map the token in the url to that (because there is no corresponding arg or env var representing just the host port). I would argue that the mapping logic is already a little "spicy" since you could theoretically have runtime args, package args, and env vars with conflicting names (so you already need to specify the resolution logic / priority for deterministic resolution). My recommendation is that the guidance for token substitution be amended to say that after checking the runtime args, package args, and env vars (in that order) that you then fall back to checking for a matching variable under each of those things (in the same order). That would support the exact case above and keep the syntax clean. I also considered a syntax like |
Good callout.
I think I'd prefer this to the blind sub-variable matching. Though it would actually be |
I could certainly live with the directed (dot) references. I can't explain exactly why, but But back to the point: I'd advocate that we specify that it's OK to omit the leading dashes for named arg matching in the url token just to make it not hurt my eyes, but maybe that's not a good enough reason ;) |
IMO, both StreamableHttpTransport and SseTransport "pattern": "^https?://[^\\s]+$" So as to at least encourage url-like values (versus free form strings) Currently StreamableHttpTransport has no "format": "uri" which will prevent tokens in SSE (the intent to allow tokens in SSE seems clear, so I assume this is just an oversight). |
Sorry I've been slow to follow up here - planning to get into it tomorrow |
@BobDickinson @connor4312 regarding the last few comments -- am I correct in reading that those changes don't affect the shape of "adding template variable support in remote transports", and are both additional changes we could do as follow on PRs? If so I will probably skip on those and let's do them in follow ups, just so I can land this. Sorry I've been slow here. |
#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
0a4ae44
to
4335162
Compare
Correct |
…es support - Add LocalTransport type (StdioTransport | StreamableHttpTransport | SseTransport) for Package context - Add RemoteTransport type extending StreamableHttp/Sse with variables field for Remote context - Update Package transport to reference LocalTransport - Update remotes to reference RemoteTransport - Update transport URL descriptions to clarify variable resolution by context - Remove unintended production_servers.json file This enables URL templating for remote servers with dedicated variable definitions, supporting multi-tenant deployments with configurable endpoints.
- Remove 'Deprecated Server Example' section that conflicts with main's intent - Update expectedServerJSONCount from 15 to 14 (12 original + 2 new URL templating examples)
I'd like to see the fix to the SSE format (which is a current bug that prevents SSE transports using templates, and fails on currently published server entries that would otherwise pass). The more flexible template resolution could be a separate PR. |
- Add 6 new test cases for remote transport variable validation: - Valid single variable in URL - Valid multiple variables in URL (hostname + path) - Invalid undefined variable in URL - Invalid missing variable definition - Valid SSE transport with variables - Valid variables defined but not used in URL - Fix IsValidRemoteURL to replace template variables before localhost check - Update IsValidTemplatedURL comment to reflect remote variable support All tests pass. Closes test coverage gap for the new remote transport variables feature.
Add pattern validation to ensure transport URLs are URL-like values while still supporting template variables for multi-tenant deployments. Changes: - Add `pattern: "^https?://[^\\s]+$"` to both StreamableHttpTransport and SseTransport URL fields in openapi.yaml - Remove `format: uri` from SseTransport URL field which was blocking template variables like {tenant_id} in URLs - Regenerate server.schema.json from openapi.yaml - Add comprehensive test coverage in schema_pattern_test.go that verifies: - Static URLs pass validation - Template variables in URLs pass validation (critical for remotes) - Free-form strings without http(s):// are rejected - URLs with spaces are rejected This fixes an issue where StreamableHttpTransport had no URL validation and SseTransport's format: uri was preventing valid template variable usage. The new pattern allows templates while still enforcing basic URL structure. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
…ample Instead of adding a new test pattern (schema_pattern_test.go), add a simple SSE transport example with template variables to the documentation which gets validated by the existing validate-examples tool. Changes: - Add SSE remote transport example with {tenant_id} template variable to generic-server-json.md - Update expectedServerJSONCount from 14 to 15 in validate-examples tool - Remove internal/validators/schema_pattern_test.go (new test pattern) This follows the existing testing patterns where schema validation happens through documentation examples rather than dedicated unit tests. The new SSE example proves that template variables work correctly with the pattern validation added in the previous commit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Instead of duplicating the variables definition in each branch of anyOf, use allOf at the top level with anyOf nested inside. This makes the schema more maintainable and follows better composition practices. Before: RemoteTransport = (StreamableHttp + variables) | (SSE + variables) After: RemoteTransport = (StreamableHttp | SSE) + variables Changes: - Change RemoteTransport from anyOf with repeated allOf to allOf with anyOf - Variables definition now appears only once at the top level - Semantically equivalent but more DRY and maintainable - All validation tests still pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
…dURL The allowTemplates boolean parameter was never used - both call sites always passed true. This was dead code left over from an earlier design where we considered disallowing templates in certain contexts. Since we now support templates in both Package and Remote contexts, the parameter serves no purpose. Changes: - Remove allowTemplates parameter from IsValidTemplatedURL signature - Update both call sites (package and remote validation) - Remove related dead code checking !allowTemplates - Simplify function logic All tests still pass - functionality unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Fair - included this. Ready for final review @BobDickinson @connor4312 @modelcontextprotocol/registry-wg |
Follow up ticket for the case @BobDickinson raised a few comments ago: #656 |
"name": "--port", | ||
"description": "Port for the server to listen on", | ||
"default": "3000", | ||
"valueHint": "port" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
valueHint
is only valid on positional arguments, not named arguments (otherwise we would have nothing to key off of except an unstable index)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed, thanks! Also added a ticket #662
Co-authored-by: Connor Peet <[email protected]>
When validating URLs with template variables in the port position (e.g., http://localhost:{--port}/mcp), the validator was incorrectly replacing the template variable with the string "placeholder", which caused URL parsing to fail since ports must be numeric. This commit fixes the issue by: - Detecting when a template variable appears in a port position (after a colon and before a slash or end of string) - Replacing port-position template variables with a numeric placeholder "8080" instead of "placeholder" - Adding a new error constant ErrInvalidPackageTransportURL to distinguish package transport errors from remote transport errors - Updating validatePackageTransport to use the correct error constant The fix allows named arguments like "--port" to be properly referenced in package transport URLs, enabling examples like: ```json { "transport": { "type": "streamable-http", "url": "http://localhost:{--port}/mcp" }, "packageArguments": [ { "type": "named", "name": "--port", "default": "3000" } ] } ``` Fixes validation of Example 15 in generic-server-json.md (line 732) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spec side lgtm
Co-authored with Claude Code, but I've closely reviewed/modified/vetted all changes; ready for final review.
Summary
This PR adds LocalTransport and RemoteTransport separation with URL template variable support for remote servers, enabling e.g. multi-tenant deployments with configurable endpoints.
Key additions:
{tenant_id}
in URLs)Problem
The current schema describes URL templating with
{curly_braces}
for transports but provides no mechanism for Remote contexts to define what those variables should resolve to, making the feature unusable for remote servers.Referenced issue: #394
us-cell1.example.com
,emea-cell1.example.com
)Solution
Schema Architecture
LocalTransport (for Package context):
{curly_braces}
reference parent Package arguments/environment variablesRemoteTransport (for Remote context):
allOf
composition - no duplication!variables
object for URL templatingKey Changes
1. Schema Updates (
openapi.yaml
,server.schema.json
):LocalTransport
union type for Package contextRemoteTransport
withallOf
composition extending base transportsLocalTransport
RemoteTransport
^https?://[^\\s]+$
to both StreamableHttp and SSEformat: uri
from SSE (was blocking template variables)2. Go Types (
pkg/model/types.go
):Transport
struct unchanged for Package contextRemoteTransport
struct for remotes withVariables
field3. Validators (
internal/validators/
):validateRemoteTransport()
validates Remote-specific constraintscollectRemoteTransportVariables()
extracts available variablesIsValidTemplatedURL()
validates template variables reference defined variablesIsValidRemoteURL()
handles template variable substitution before localhost check4. Documentation Examples (
generic-server-json.md
):{tenant_id}
){tenant_id}
){port}
)validate-examples
toolExample: Remote Transport with Variables
Clients configure the variables, and
{tenant_id}
and{region}
get replaced to connect to tenant-specific endpoints likehttps://api.example.com/mcp/us-cell1/east
.Architecture Details
Context-based variable resolution:
{port}
references--port
argument orPORT
env var from parent Package{tenant_id}
referencesvariables.tenant_id
defined in the transport itselfallOf
, not duplicatedFollow-on Work
Follow-on work will be done to adopt the feedback in #570 (comment) regarding variable naming/prioritzation/scoping conventions, but should not have any impact on the shape being introduced here (just validations).