11package config
22
33import (
4+ "encoding/json"
45 "fmt"
6+ "net/http"
57 neturl "net/url"
68 "path/filepath"
79 "strings"
@@ -19,7 +21,7 @@ const (
1921)
2022
2123// DetectRegistryType determines if input is a URL or file path and returns cleaned path
22- func DetectRegistryType (input string ) (registryType string , cleanPath string ) {
24+ func DetectRegistryType (input string , allowPrivateIPs bool ) (registryType string , cleanPath string ) {
2325 // Check for explicit file:// protocol
2426 if strings .HasPrefix (input , "file://" ) {
2527 return RegistryTypeFile , strings .TrimPrefix (input , "file://" )
@@ -28,17 +30,72 @@ func DetectRegistryType(input string) (registryType string, cleanPath string) {
2830 // Check for HTTP/HTTPS URLs
2931 if networking .IsURL (input ) {
3032 // If URL ends with .json, treat as static registry file
31- // Otherwise, treat as MCP Registry API endpoint
3233 if strings .HasSuffix (input , ".json" ) {
3334 return RegistryTypeURL , input
3435 }
35- return RegistryTypeAPI , input
36+
37+ // For URLs without .json extension, probe to determine the type
38+ registryType := probeRegistryURL (input , allowPrivateIPs )
39+ return registryType , input
3640 }
3741
3842 // Default: treat as file path
3943 return RegistryTypeFile , filepath .Clean (input )
4044}
4145
46+ // probeRegistryURL attempts to determine if a URL is a static JSON file or an API endpoint
47+ // by checking if the URL returns valid ToolHive registry JSON or has an /openapi.yaml endpoint.
48+ func probeRegistryURL (url string , allowPrivateIPs bool ) string {
49+ // Create HTTP client for probing with user's private IP preference
50+ client , err := networking .NewHttpClientBuilder ().WithPrivateIPs (allowPrivateIPs ).Build ()
51+ if err != nil {
52+ // If we can't create a client, default to static JSON
53+ return RegistryTypeURL
54+ }
55+
56+ // First, try to fetch and parse as ToolHive registry JSON
57+ if isValidRegistryJSON (client , url ) {
58+ return RegistryTypeURL
59+ }
60+
61+ // If not valid JSON, check for /openapi.yaml endpoint (MCP Registry API)
62+ openapiURL , err := neturl .JoinPath (url , "openapi.yaml" )
63+ if err == nil {
64+ resp , err := client .Head (openapiURL )
65+ if err == nil {
66+ _ = resp .Body .Close ()
67+ // If openapi.yaml exists (200 OK), treat as API endpoint
68+ if resp .StatusCode == http .StatusOK {
69+ return RegistryTypeAPI
70+ }
71+ }
72+ }
73+
74+ // Default to static JSON file (validation will catch errors later)
75+ return RegistryTypeURL
76+ }
77+
78+ // isValidRegistryJSON checks if a URL returns valid ToolHive registry JSON
79+ func isValidRegistryJSON (client * http.Client , url string ) bool {
80+ resp , err := client .Get (url )
81+ if err != nil {
82+ return false
83+ }
84+ defer resp .Body .Close ()
85+
86+ // Try to parse as JSON with registry structure
87+ // We just check for basic registry fields to avoid pulling in the full types package
88+ var data map [string ]interface {}
89+ if err := json .NewDecoder (resp .Body ).Decode (& data ); err != nil {
90+ return false
91+ }
92+
93+ // Check if it has registry-like structure (servers or remoteServers fields)
94+ _ , hasServers := data ["servers" ]
95+ _ , hasRemoteServers := data ["remoteServers" ]
96+ return hasServers || hasRemoteServers
97+ }
98+
4299// setRegistryURL validates and sets a registry URL using the provided provider
43100func setRegistryURL (provider Provider , registryURL string , allowPrivateRegistryIp bool ) error {
44101 // Validate URL scheme
0 commit comments