@@ -3,14 +3,17 @@ package commands
33import (
44 "bufio"
55 "context"
6+ "encoding/base64"
67 "encoding/json"
78 "fmt"
9+ "net/url"
810 "strings"
911 "time"
1012
1113 "github.com/f/mcptools/pkg/alias"
1214 "github.com/f/mcptools/pkg/jsonutils"
1315 "github.com/mark3labs/mcp-go/client"
16+ "github.com/mark3labs/mcp-go/client/transport"
1417 "github.com/mark3labs/mcp-go/mcp"
1518
1619 "github.com/spf13/cobra"
@@ -26,15 +29,70 @@ func IsHTTP(str string) bool {
2629 return strings .HasPrefix (str , "http://" ) || strings .HasPrefix (str , "https://" ) || strings .HasPrefix (str , "localhost:" )
2730}
2831
32+ // buildAuthHeader builds an Authorization header from the available auth options.
33+ // It returns the header value and a cleaned URL (with embedded credentials removed).
34+ func buildAuthHeader (originalURL string ) (string , string , error ) {
35+ cleanURL := originalURL
36+
37+ // First, check if we have explicit auth-user flag with username:password format
38+ if AuthUser != "" {
39+ // Parse username:password format
40+ if ! strings .Contains (AuthUser , ":" ) {
41+ return "" , originalURL , fmt .Errorf ("auth-user must be in username:password format (missing colon)" )
42+ }
43+
44+ parts := strings .SplitN (AuthUser , ":" , 2 )
45+ username := parts [0 ]
46+ password := parts [1 ]
47+
48+ // Allow empty username or password, but not both
49+ if username == "" && password == "" {
50+ // Both empty, treat as no auth
51+ } else {
52+ // Create basic auth header
53+ auth := base64 .StdEncoding .EncodeToString ([]byte (username + ":" + password ))
54+ header := "Basic " + auth
55+ return header , cleanURL , nil
56+ }
57+ }
58+
59+ // Check for custom auth header
60+ if AuthHeader != "" {
61+ return AuthHeader , cleanURL , nil
62+ }
63+
64+ // Extract credentials from URL if embedded
65+ parsedURL , err := url .Parse (originalURL )
66+ if err != nil {
67+ return "" , originalURL , err
68+ }
69+
70+ if parsedURL .User != nil {
71+ username := parsedURL .User .Username ()
72+ password , _ := parsedURL .User .Password ()
73+
74+ if username != "" {
75+ // Create basic auth header
76+ auth := base64 .StdEncoding .EncodeToString ([]byte (username + ":" + password ))
77+
78+ // Clean the URL by removing user info
79+ parsedURL .User = nil
80+ cleanURL = parsedURL .String ()
81+
82+ return "Basic " + auth , cleanURL , nil
83+ }
84+ }
85+
86+ return "" , cleanURL , nil
87+ }
88+
2989// CreateClientFunc is the function used to create MCP clients.
3090// This can be replaced in tests to use a mock transport.
3191var CreateClientFunc = func (args []string , _ ... client.ClientOption ) (* client.Client , error ) {
3292 if len (args ) == 0 {
3393 return nil , ErrCommandRequired
3494 }
3595
36- // opts = append(opts, client.SetShowServerLogs(ShowServerLogs))
37-
3896 // Check if the first argument is an alias
3997 if len (args ) == 1 {
4098 server , found := alias .GetServerCommand (args [0 ])
@@ -51,13 +109,35 @@ var CreateClientFunc = func(args []string, _ ...client.ClientOption) (*client.Cl
51109 if TransportOption != "http" && TransportOption != "sse" {
52110 return nil , fmt .Errorf ("invalid transport option: %s (supported: http, sse)" , TransportOption )
53111 }
54-
112+
113+ // Build authentication header
114+ authHeader , cleanURL , authErr := buildAuthHeader (args [0 ])
115+ if authErr != nil {
116+ return nil , fmt .Errorf ("failed to parse authentication: %w" , authErr )
117+ }
118+
119+ // Create headers map if authentication is provided
120+ headers := make (map [string ]string )
121+ if authHeader != "" {
122+ headers ["Authorization" ] = authHeader
123+ }
124+
55125 if TransportOption == "sse" {
56- c , err = client .NewSSEMCPClient (args [0 ])
126+ // For SSE transport, use transport.ClientOption
127+ if len (headers ) > 0 {
128+ c , err = client .NewSSEMCPClient (cleanURL , transport .WithHeaders (headers ))
129+ } else {
130+ c , err = client .NewSSEMCPClient (cleanURL )
131+ }
57132 } else {
58- // Default to streamable HTTP
59- c , err = client .NewStreamableHttpClient (args [0 ])
133+ // For StreamableHTTP transport, use transport.StreamableHTTPCOption
134+ if len (headers ) > 0 {
135+ c , err = client .NewStreamableHttpClient (cleanURL , transport .WithHTTPHeaders (headers ))
136+ } else {
137+ c , err = client .NewStreamableHttpClient (cleanURL )
138+ }
60139 }
140+
61141 if err != nil {
62142 return nil , err
63143 }
@@ -83,7 +163,14 @@ var CreateClientFunc = func(args []string, _ ...client.ClientOption) (*client.Cl
83163 done := make (chan error , 1 )
84164
85165 go func () {
86- _ , err := c .Initialize (context .Background (), mcp.InitializeRequest {})
166+ initRequest := mcp.InitializeRequest {}
167+ initRequest .Params .ProtocolVersion = "2024-11-05"
168+ initRequest .Params .Capabilities = mcp.ClientCapabilities {}
169+ initRequest .Params .ClientInfo = mcp.Implementation {
170+ Name : "mcptools" ,
171+ Version : "1.0.0" ,
172+ }
173+ _ , err := c .Initialize (context .Background (), initRequest )
87174 done <- err
88175 }()
89176
@@ -121,6 +208,12 @@ func ProcessFlags(args []string) []string {
121208 case args [i ] == FlagServerLogs :
122209 ShowServerLogs = true
123210 i ++
211+ case args [i ] == FlagAuthUser && i + 1 < len (args ):
212+ AuthUser = args [i + 1 ]
213+ i += 2
214+ case args [i ] == FlagAuthHeader && i + 1 < len (args ):
215+ AuthHeader = args [i + 1 ]
216+ i += 2
124217 default :
125218 parsedArgs = append (parsedArgs , args [i ])
126219 i ++
0 commit comments