Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ tasks:
platforms: [linux, darwin]
internal: true
cmds:
- go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest
# we have to use ldflags to avoid the LC_DYSYMTAB linker error.
# Temporarily bypass gotestfmt due to panic issue with empty package names
# - go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest
# we have to use ldflags to avoid the LC_DYSYMTAB linker error.
# https://github.com/stacklok/toolhive/issues/1687
- go test -ldflags=-extldflags=-Wl,-w -v -json -race $(go list ./... | grep -v '/test/e2e' | grep -v '/cmd/thv-operator/test-integration') | gotestfmt -hide "all"
# - go test -ldflags=-extldflags=-Wl,-w -v -json -race $(go list ./... | grep -v '/test/e2e' | grep -v '/cmd/thv-operator/test-integration') | gotestfmt -hide "all"
- go test -ldflags=-extldflags=-Wl,-w -v -race $(go list ./... | grep -v '/test/e2e' | grep -v '/cmd/thv-operator/test-integration')

test-windows:
desc: Run unit tests (excluding e2e tests) on Windows with race detection
Expand Down
79 changes: 40 additions & 39 deletions cmd/thv/app/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import (
"text/tabwriter"
"time"

"github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/spf13/cobra"

"github.com/stacklok/toolhive/pkg/logger"
Expand Down Expand Up @@ -119,23 +118,23 @@ func mcpListCmdFunc(cmd *cobra.Command, _ []string) error {
data := make(map[string]interface{})

// List tools
if tools, err := mcpClient.ListTools(ctx, mcp.ListToolsRequest{}); err != nil {
if tools, err := mcpClient.ListTools(ctx, &mcp.ListToolsParams{}); err != nil {
logger.Warnf("Failed to list tools: %v", err)
data["tools"] = []mcp.Tool{}
} else {
data["tools"] = tools.Tools
}

// List resources
if resources, err := mcpClient.ListResources(ctx, mcp.ListResourcesRequest{}); err != nil {
if resources, err := mcpClient.ListResources(ctx, &mcp.ListResourcesParams{}); err != nil {
logger.Warnf("Failed to list resources: %v", err)
data["resources"] = []mcp.Resource{}
} else {
data["resources"] = resources.Resources
}

// List prompts
if prompts, err := mcpClient.ListPrompts(ctx, mcp.ListPromptsRequest{}); err != nil {
if prompts, err := mcpClient.ListPrompts(ctx, &mcp.ListPromptsParams{}); err != nil {
logger.Warnf("Failed to list prompts: %v", err)
data["prompts"] = []mcp.Prompt{}
} else {
Expand Down Expand Up @@ -166,7 +165,7 @@ func mcpListToolsCmdFunc(cmd *cobra.Command, _ []string) error {
return err
}

result, err := mcpClient.ListTools(ctx, mcp.ListToolsRequest{})
result, err := mcpClient.ListTools(ctx, &mcp.ListToolsParams{})
if err != nil {
return fmt.Errorf("failed to list tools: %w", err)
}
Expand Down Expand Up @@ -195,7 +194,7 @@ func mcpListResourcesCmdFunc(cmd *cobra.Command, _ []string) error {
return err
}

result, err := mcpClient.ListResources(ctx, mcp.ListResourcesRequest{})
result, err := mcpClient.ListResources(ctx, &mcp.ListResourcesParams{})
if err != nil {
return fmt.Errorf("failed to list resources: %w", err)
}
Expand Down Expand Up @@ -224,7 +223,7 @@ func mcpListPromptsCmdFunc(cmd *cobra.Command, _ []string) error {
return err
}

result, err := mcpClient.ListPrompts(ctx, mcp.ListPromptsRequest{})
result, err := mcpClient.ListPrompts(ctx, &mcp.ListPromptsParams{})
if err != nil {
return fmt.Errorf("failed to list prompts: %w", err)
}
Expand Down Expand Up @@ -262,29 +261,45 @@ func resolveServerURL(ctx context.Context, serverInput string) (string, error) {
}

// createMCPClient creates an MCP client based on the server URL and transport type
func createMCPClient(serverURL string) (*client.Client, error) {
func createMCPClient(serverURL string) (*mcp.ClientSession, error) {
transportType := determineTransportType(serverURL, mcpTransport)

// Create the MCP client
client := mcp.NewClient(
&mcp.Implementation{
Name: "thv-mcp-cli",
Version: versions.Version,
},
&mcp.ClientOptions{},
)

var transport mcp.Transport

switch transportType {
case types.TransportTypeSSE:
mcpClient, err := client.NewSSEMCPClient(serverURL)
if err != nil {
return nil, fmt.Errorf("failed to create SSE MCP client: %w", err)
transport = &mcp.SSEClientTransport{
Endpoint: serverURL,
}
return mcpClient, nil
case types.TransportTypeStreamableHTTP:
mcpClient, err := client.NewStreamableHttpClient(serverURL)
if err != nil {
return nil, fmt.Errorf("failed to create Streamable HTTP MCP client: %w", err)
transport = &mcp.StreamableClientTransport{
Endpoint: serverURL,
MaxRetries: 5,
}
return mcpClient, nil
case types.TransportTypeStdio:
return nil, fmt.Errorf("stdio transport is not supported for MCP client connections")
case types.TransportTypeInspector:
return nil, fmt.Errorf("inspector transport is not supported for MCP client connections")
default:
return nil, fmt.Errorf("unsupported transport type: %s", transportType)
}

// Connect using the transport
session, err := client.Connect(context.Background(), transport, nil)
if err != nil {
return nil, fmt.Errorf("failed to connect to MCP server: %w", err)
}

return session, nil
}

// determineTransportType determines the transport type based on URL path and user preference
Expand Down Expand Up @@ -325,29 +340,15 @@ func determineTransportType(serverURL, transportFlag string) types.TransportType
}

// initializeMCPClient initializes the MCP client connection
func initializeMCPClient(ctx context.Context, mcpClient *client.Client) error {
// Start the transport
if err := mcpClient.Start(ctx); err != nil {
return fmt.Errorf("failed to start MCP transport: %w", err)
func initializeMCPClient(ctx context.Context, mcpClient *mcp.ClientSession) error {
// Initialization happens during Connect, just verify we're connected
if mcpClient == nil {
return fmt.Errorf("client session not connected")
}

// Initialize the connection
initRequest := mcp.InitializeRequest{}
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.Capabilities = mcp.ClientCapabilities{
// Basic client capabilities for listing
}
versionInfo := versions.GetVersionInfo()
initRequest.Params.ClientInfo = mcp.Implementation{
Name: "toolhive-cli",
Version: versionInfo.Version,
}

_, err := mcpClient.Initialize(ctx, initRequest)
if err != nil {
return fmt.Errorf("failed to initialize MCP client: %w", err)
result := mcpClient.InitializeResult()
if result == nil {
return fmt.Errorf("client session not initialized")
}

return nil
}

Expand Down Expand Up @@ -438,7 +439,7 @@ func outputMCPPrompts(w *tabwriter.Writer, data map[string]interface{}) bool {
}

// formatPromptArguments formats the prompt arguments for display
func formatPromptArguments(arguments []mcp.PromptArgument) string {
func formatPromptArguments(arguments []*mcp.PromptArgument) string {
argCount := len(arguments)
if argCount == 0 {
return "0"
Expand Down
7 changes: 2 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ require (
github.com/google/uuid v1.6.0
github.com/lestrrat-go/httprc/v3 v3.0.1
github.com/lestrrat-go/jwx/v3 v3.0.11
github.com/mark3labs/mcp-go v0.41.1
github.com/modelcontextprotocol/go-sdk v1.0.0
github.com/olekukonko/tablewriter v1.1.0
github.com/onsi/ginkgo/v2 v2.26.0
github.com/onsi/gomega v1.38.2
Expand Down Expand Up @@ -78,10 +78,8 @@ require (
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
Expand Down Expand Up @@ -150,6 +148,7 @@ require (
github.com/google/certificate-transparency-go v1.3.2 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/jsonschema-go v0.3.0 // indirect
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
Expand All @@ -163,7 +162,6 @@ require (
github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b // indirect
github.com/in-toto/attestation v1.1.2 // indirect
github.com/in-toto/in-toto-golang v0.9.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect
github.com/josharian/intern v1.0.0 // indirect
Expand Down Expand Up @@ -240,7 +238,6 @@ require (
github.com/transparency-dev/merkle v0.0.2 // indirect
github.com/transparency-dev/tessera v1.0.0-rc3 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
Expand Down
14 changes: 4 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -719,16 +719,12 @@ github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/cedar-policy/cedar-go v1.2.6 h1:q6f1sRxhoBG7lnK/fH6oBG33ruf2yIpcfcPXNExANa0=
github.com/cedar-policy/cedar-go v1.2.6/go.mod h1:h5+3CVW1oI5LXVskJG+my9TFCYI5yjh/+Ul3EJie6MI=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
Expand Down Expand Up @@ -1096,6 +1092,8 @@ github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
Expand Down Expand Up @@ -1208,8 +1206,6 @@ github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
Expand Down Expand Up @@ -1346,8 +1342,6 @@ github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuz
github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mark3labs/mcp-go v0.41.1 h1:w78eWfiQam2i8ICL7AL0WFiq7KHNJQ6UB53ZVtH4KGA=
github.com/mark3labs/mcp-go v0.41.1/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g=
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
Expand Down Expand Up @@ -1395,6 +1389,8 @@ github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7z
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modelcontextprotocol/go-sdk v1.0.0 h1:Z4MSjLi38bTgLrd/LjSmofqRqyBiVKRyQSJgw8q8V74=
github.com/modelcontextprotocol/go-sdk v1.0.0/go.mod h1:nYtYQroQ2KQiM0/SbyEPUWQ6xs4B95gJjEalc9AQyOs=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down Expand Up @@ -1649,8 +1645,6 @@ github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXV
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
Expand Down
Loading
Loading