Skip to content

Commit 217bf2e

Browse files
authored
feat: enhance thv mcp list to accept server names in addition to URLs (#1488)
Signed-off-by: Juan Antonio Osorio <[email protected]>
1 parent 765e3da commit 217bf2e

File tree

5 files changed

+67
-13
lines changed

5 files changed

+67
-13
lines changed

cmd/thv/app/mcp.go

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/stacklok/toolhive/pkg/transport/streamable"
2020
"github.com/stacklok/toolhive/pkg/transport/types"
2121
"github.com/stacklok/toolhive/pkg/versions"
22+
"github.com/stacklok/toolhive/pkg/workloads"
2223
)
2324

2425
var (
@@ -86,7 +87,7 @@ func newMCPCommand() *cobra.Command {
8687
}
8788

8889
func addMCPFlags(cmd *cobra.Command) {
89-
cmd.Flags().StringVar(&mcpServerURL, "server", "", "MCP server URL (required)")
90+
cmd.Flags().StringVar(&mcpServerURL, "server", "", "MCP server URL or name from ToolHive registry (required)")
9091
cmd.Flags().StringVar(&mcpFormat, "format", FormatText, "Output format (json or text)")
9192
cmd.Flags().DurationVar(&mcpTimeout, "timeout", 30*time.Second, "Connection timeout")
9293
cmd.Flags().StringVar(&mcpTransport, "transport", "auto", "Transport type (auto, sse, streamable-http)")
@@ -98,7 +99,13 @@ func mcpListCmdFunc(cmd *cobra.Command, _ []string) error {
9899
ctx, cancel := context.WithTimeout(cmd.Context(), mcpTimeout)
99100
defer cancel()
100101

101-
mcpClient, err := createMCPClient()
102+
// Resolve server URL if it's a name
103+
serverURL, err := resolveServerURL(ctx, mcpServerURL)
104+
if err != nil {
105+
return err
106+
}
107+
108+
mcpClient, err := createMCPClient(serverURL)
102109
if err != nil {
103110
return err
104111
}
@@ -143,7 +150,13 @@ func mcpListToolsCmdFunc(cmd *cobra.Command, _ []string) error {
143150
ctx, cancel := context.WithTimeout(cmd.Context(), mcpTimeout)
144151
defer cancel()
145152

146-
mcpClient, err := createMCPClient()
153+
// Resolve server URL if it's a name
154+
serverURL, err := resolveServerURL(ctx, mcpServerURL)
155+
if err != nil {
156+
return err
157+
}
158+
159+
mcpClient, err := createMCPClient(serverURL)
147160
if err != nil {
148161
return err
149162
}
@@ -166,7 +179,13 @@ func mcpListResourcesCmdFunc(cmd *cobra.Command, _ []string) error {
166179
ctx, cancel := context.WithTimeout(cmd.Context(), mcpTimeout)
167180
defer cancel()
168181

169-
mcpClient, err := createMCPClient()
182+
// Resolve server URL if it's a name
183+
serverURL, err := resolveServerURL(ctx, mcpServerURL)
184+
if err != nil {
185+
return err
186+
}
187+
188+
mcpClient, err := createMCPClient(serverURL)
170189
if err != nil {
171190
return err
172191
}
@@ -189,7 +208,13 @@ func mcpListPromptsCmdFunc(cmd *cobra.Command, _ []string) error {
189208
ctx, cancel := context.WithTimeout(cmd.Context(), mcpTimeout)
190209
defer cancel()
191210

192-
mcpClient, err := createMCPClient()
211+
// Resolve server URL if it's a name
212+
serverURL, err := resolveServerURL(ctx, mcpServerURL)
213+
if err != nil {
214+
return err
215+
}
216+
217+
mcpClient, err := createMCPClient(serverURL)
193218
if err != nil {
194219
return err
195220
}
@@ -207,19 +232,48 @@ func mcpListPromptsCmdFunc(cmd *cobra.Command, _ []string) error {
207232
return outputMCPData(map[string]interface{}{"prompts": result.Prompts}, mcpFormat)
208233
}
209234

235+
// resolveServerURL resolves a server name to a URL or returns the URL if it's already a URL
236+
func resolveServerURL(ctx context.Context, serverInput string) (string, error) {
237+
// Check if it's already a URL
238+
if strings.HasPrefix(serverInput, "http://") || strings.HasPrefix(serverInput, "https://") {
239+
return serverInput, nil
240+
}
241+
242+
// Try to get the workload by name
243+
manager, err := workloads.NewManager(ctx)
244+
if err != nil {
245+
return "", fmt.Errorf("failed to create workload manager: %w", err)
246+
}
247+
248+
workload, err := manager.GetWorkload(ctx, serverInput)
249+
if err != nil {
250+
return "", fmt.Errorf(
251+
"server '%s' not found in running workloads. "+
252+
"Please ensure the server is running or provide a valid URL", serverInput)
253+
}
254+
255+
// Check if the workload is running
256+
if workload.Status != "running" {
257+
return "", fmt.Errorf("server '%s' is not running (status: %s). "+
258+
"Please start it first using 'thv run %s'", serverInput, workload.Status, serverInput)
259+
}
260+
261+
return workload.URL, nil
262+
}
263+
210264
// createMCPClient creates an MCP client based on the server URL and transport type
211-
func createMCPClient() (*client.Client, error) {
212-
transportType := determineTransportType(mcpServerURL, mcpTransport)
265+
func createMCPClient(serverURL string) (*client.Client, error) {
266+
transportType := determineTransportType(serverURL, mcpTransport)
213267

214268
switch transportType {
215269
case types.TransportTypeSSE:
216-
mcpClient, err := client.NewSSEMCPClient(mcpServerURL)
270+
mcpClient, err := client.NewSSEMCPClient(serverURL)
217271
if err != nil {
218272
return nil, fmt.Errorf("failed to create SSE MCP client: %w", err)
219273
}
220274
return mcpClient, nil
221275
case types.TransportTypeStreamableHTTP:
222-
mcpClient, err := client.NewStreamableHttpClient(mcpServerURL)
276+
mcpClient, err := client.NewStreamableHttpClient(serverURL)
223277
if err != nil {
224278
return nil, fmt.Errorf("failed to create Streamable HTTP MCP client: %w", err)
225279
}

docs/cli/thv_mcp_list.md

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/cli/thv_mcp_list_prompts.md

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/cli/thv_mcp_list_resources.md

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/cli/thv_mcp_list_tools.md

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)