|
1 | | -// Package converters provides conversion functions from toolhive ImageMetadata/RemoteServerMetadata formats |
2 | | -// to upstream MCP ServerJSON format. |
| 1 | +// Package converters provides bidirectional conversion between toolhive registry formats |
| 2 | +// and the upstream MCP (Model Context Protocol) ServerJSON format. |
| 3 | +// |
| 4 | +// The package supports two conversion directions: |
| 5 | +// - toolhive → upstream: ImageMetadata/RemoteServerMetadata → ServerJSON (this file) |
| 6 | +// - upstream → toolhive: ServerJSON → ImageMetadata/RemoteServerMetadata (upstream_to_toolhive.go) |
| 7 | +// |
| 8 | +// Toolhive-specific fields (permissions, provenance, metadata) are stored in the upstream |
| 9 | +// format's publisher extensions under "io.github.stacklok", allowing additional metadata |
| 10 | +// while maintaining compatibility with the standard MCP registry format. |
3 | 11 | package converters |
4 | 12 |
|
5 | 13 | import ( |
@@ -35,6 +43,17 @@ func ImageMetadataToServerJSON(name string, imageMetadata *registry.ImageMetadat |
35 | 43 | URL: imageMetadata.RepositoryURL, |
36 | 44 | Source: "github", // Assume GitHub |
37 | 45 | } |
| 46 | + } else { |
| 47 | + // Use toolhive-registry as fallback when no repository URL is available. |
| 48 | + // This is necessary for schema validation - the upstream Repository field is a struct |
| 49 | + // (not a pointer), so it can't be omitted with omitempty and would serialize as |
| 50 | + // empty strings {"url": "", "source": ""}, which fails URI format validation. |
| 51 | + // Using the toolhive-registry URL is reasonable since it's where these servers |
| 52 | + // are registered and documented. |
| 53 | + serverJSON.Repository = model.Repository{ |
| 54 | + URL: "https://github.com/stacklok/toolhive-registry", |
| 55 | + Source: "github", |
| 56 | + } |
38 | 57 | } |
39 | 58 |
|
40 | 59 | // Create package |
@@ -73,6 +92,17 @@ func RemoteServerMetadataToServerJSON(name string, remoteMetadata *registry.Remo |
73 | 92 | URL: remoteMetadata.RepositoryURL, |
74 | 93 | Source: "github", // Assume GitHub |
75 | 94 | } |
| 95 | + } else { |
| 96 | + // Use toolhive-registry as fallback when no repository URL is available. |
| 97 | + // This is necessary for schema validation - the upstream Repository field is a struct |
| 98 | + // (not a pointer), so it can't be omitted with omitempty and would serialize as |
| 99 | + // empty strings {"url": "", "source": ""}, which fails URI format validation. |
| 100 | + // Using the toolhive-registry URL is reasonable since it's where these servers |
| 101 | + // are registered and documented. |
| 102 | + serverJSON.Repository = model.Repository{ |
| 103 | + URL: "https://github.com/stacklok/toolhive-registry", |
| 104 | + Source: "github", |
| 105 | + } |
76 | 106 | } |
77 | 107 |
|
78 | 108 | // Create remote |
@@ -115,12 +145,15 @@ func createPackagesFromImageMetadata(imageMetadata *registry.ImageMetadata) []mo |
115 | 145 | } |
116 | 146 |
|
117 | 147 | // Add URL for non-stdio transports |
| 148 | + // Note: We use localhost as the host because container-based MCP servers run locally |
| 149 | + // and are accessed via port forwarding. The actual container may listen on 0.0.0.0, |
| 150 | + // but clients connect via localhost on the host machine. |
118 | 151 | if transportType == model.TransportTypeStreamableHTTP || transportType == model.TransportTypeSSE { |
119 | 152 | if imageMetadata.TargetPort > 0 { |
120 | 153 | // Include port in URL if explicitly set |
121 | 154 | transport.URL = fmt.Sprintf("http://localhost:%d", imageMetadata.TargetPort) |
122 | 155 | } else { |
123 | | - // No port specified - use URL without port |
| 156 | + // No port specified - use URL without port (standard HTTP port 80) |
124 | 157 | transport.URL = "http://localhost" |
125 | 158 | } |
126 | 159 | } |
|
0 commit comments