8
8
"net/url"
9
9
"os"
10
10
"os/signal"
11
+ "strings"
11
12
"syscall"
12
13
"time"
13
14
@@ -17,6 +18,7 @@ import (
17
18
"github.com/stacklok/toolhive/pkg/container/runtime"
18
19
"github.com/stacklok/toolhive/pkg/groups"
19
20
"github.com/stacklok/toolhive/pkg/logger"
21
+ "github.com/stacklok/toolhive/pkg/networking"
20
22
"github.com/stacklok/toolhive/pkg/process"
21
23
"github.com/stacklok/toolhive/pkg/runner"
22
24
"github.com/stacklok/toolhive/pkg/workloads"
@@ -62,7 +64,7 @@ ToolHive supports five ways to run an MCP server:
62
64
63
65
5. Remote MCP server:
64
66
65
- $ thv run --remote <URL> [--name <name>]
67
+ $ thv run <URL> [--name <name>]
66
68
67
69
Runs a remote MCP server as a workload, proxying requests to the specified URL.
68
70
This allows remote MCP servers to be managed like local workloads with full
@@ -75,10 +77,6 @@ permission profile. Additional configuration can be provided via flags.`,
75
77
if runFlags .FromConfig != "" {
76
78
return nil
77
79
}
78
- // If --remote is provided, no args are required
79
- if runFlags .RemoteURL != "" {
80
- return nil
81
- }
82
80
// Otherwise, require at least 1 argument
83
81
return cobra .MinimumNArgs (1 )(cmd , args )
84
82
},
@@ -127,6 +125,7 @@ func cleanupAndWait(workloadManager workloads.Manager, name string, cancel conte
127
125
}
128
126
}
129
127
128
+ // nolint:gocyclo // This function is complex by design
130
129
func runCmdFunc (cmd * cobra.Command , args []string ) error {
131
130
ctx := cmd .Context ()
132
131
@@ -136,21 +135,23 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
136
135
}
137
136
138
137
// Get the name of the MCP server to run.
139
- // This may be a server name from the registry, a container image, or a protocol scheme.
140
- // When using --from-config or --remote, no args are required
138
+ // This may be a server name from the registry, a container image, a protocol scheme, or a remote URL.
141
139
var serverOrImage string
142
140
if len (args ) > 0 {
143
141
serverOrImage = args [0 ]
144
142
}
145
143
146
- // If --remote is provided but no name is given, generate a name from the URL
147
- if runFlags .RemoteURL != "" && runFlags .Name == "" {
148
- // Extract a name from the remote URL
149
- name , err := deriveRemoteName ()
150
- if err != nil {
151
- return err
144
+ // Check if the server name is actually a URL (remote server)
145
+ if serverOrImage != "" && networking .IsURL (serverOrImage ) {
146
+ runFlags .RemoteURL = serverOrImage
147
+ // If no name is given, generate a name from the URL
148
+ if runFlags .Name == "" {
149
+ name , err := deriveRemoteName (serverOrImage )
150
+ if err != nil {
151
+ return err
152
+ }
153
+ runFlags .Name = name
152
154
}
153
- runFlags .Name = name
154
155
}
155
156
156
157
// Process command arguments using os.Args to find everything after --
@@ -205,17 +206,26 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
205
206
return workloadManager .RunWorkloadDetached (ctx , runnerConfig )
206
207
}
207
208
208
- func deriveRemoteName () (string , error ) {
209
- parsedURL , err := url .Parse (runFlags .RemoteURL )
209
+ // deriveRemoteName extracts a name from a remote URL
210
+ func deriveRemoteName (remoteURL string ) (string , error ) {
211
+ parsedURL , err := url .Parse (remoteURL )
210
212
if err != nil {
211
- return "" , fmt .Errorf ("invalid remote URL: %v " , err )
213
+ return "" , fmt .Errorf ("invalid remote URL: %w " , err )
212
214
}
215
+
213
216
// Use the hostname as the base name
214
217
hostname := parsedURL .Hostname ()
215
218
if hostname == "" {
216
- hostname = "remote"
219
+ return "" , fmt . Errorf ( "could not extract hostname from URL: %s" , remoteURL )
217
220
}
218
- return fmt .Sprintf ("%s-remote" , hostname ), nil
221
+
222
+ // Remove common TLDs and use the main domain name
223
+ parts := strings .Split (hostname , "." )
224
+ if len (parts ) >= 2 {
225
+ return parts [len (parts )- 2 ], nil
226
+ }
227
+
228
+ return hostname , nil
219
229
}
220
230
221
231
func runForeground (ctx context.Context , workloadManager workloads.Manager , runnerConfig * runner.RunConfig ) error {
0 commit comments