Skip to content

Commit 83b8ae0

Browse files
authored
Merge pull request #72 from liemle3893/master
Add comprehensive HTTP authentication support
2 parents f8dff5d + 3ef1b38 commit 83b8ae0

File tree

6 files changed

+140
-19
lines changed

6 files changed

+140
-19
lines changed

cmd/mcptools/commands/call.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ func CallCmd() *cobra.Command {
4848
case (cmdArgs[i] == FlagParams || cmdArgs[i] == FlagParamsShort) && i+1 < len(cmdArgs):
4949
ParamsString = cmdArgs[i+1]
5050
i += 2
51+
case (cmdArgs[i] == FlagTransport) && i+1 < len(cmdArgs):
52+
TransportOption = cmdArgs[i+1]
53+
i += 2
54+
case (cmdArgs[i] == FlagAuthUser) && i+1 < len(cmdArgs):
55+
AuthUser = cmdArgs[i+1]
56+
i += 2
57+
case (cmdArgs[i] == FlagAuthHeader) && i+1 < len(cmdArgs):
58+
AuthHeader = cmdArgs[i+1]
59+
i += 2
5160
case !entityExtracted:
5261
entityName = cmdArgs[i]
5362
entityExtracted = true

cmd/mcptools/commands/root.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ import (
99

1010
// flags.
1111
const (
12-
FlagFormat = "--format"
13-
FlagFormatShort = "-f"
14-
FlagParams = "--params"
15-
FlagParamsShort = "-p"
16-
FlagHelp = "--help"
17-
FlagHelpShort = "-h"
18-
FlagServerLogs = "--server-logs"
19-
FlagTransport = "--transport"
12+
FlagFormat = "--format"
13+
FlagFormatShort = "-f"
14+
FlagParams = "--params"
15+
FlagParamsShort = "-p"
16+
FlagHelp = "--help"
17+
FlagHelpShort = "-h"
18+
FlagServerLogs = "--server-logs"
19+
FlagTransport = "--transport"
20+
FlagAuthUser = "--auth-user"
21+
FlagAuthHeader = "--auth-header"
2022
)
2123

2224
// entity types.
@@ -38,6 +40,10 @@ var (
3840
// TransportOption is the transport option for HTTP connections, valid values are "sse" and "http".
3941
// Default is "http" (streamable HTTP).
4042
TransportOption = "http"
43+
// AuthUser contains username:password for basic authentication.
44+
AuthUser string
45+
// AuthHeader is a custom Authorization header.
46+
AuthHeader string
4147
)
4248

4349
// RootCmd creates the root command.
@@ -53,6 +59,8 @@ It allows you to discover and call tools, list resources, and interact with MCP-
5359
cmd.PersistentFlags().
5460
StringVarP(&ParamsString, "params", "p", "{}", "JSON string of parameters to pass to the tool (for call command)")
5561
cmd.PersistentFlags().StringVar(&TransportOption, "transport", "http", "HTTP transport type (http, sse)")
62+
cmd.PersistentFlags().StringVar(&AuthUser, "auth-user", "", "Basic authentication in username:password format")
63+
cmd.PersistentFlags().StringVar(&AuthHeader, "auth-header", "", "Custom Authorization header (e.g., 'Bearer token' or 'Basic base64credentials')")
5664

5765
return cmd
5866
}

cmd/mcptools/commands/shell.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,24 @@ func ShellCmd() *cobra.Command { //nolint:gocyclo
3131
cmdArgs := args
3232
parsedArgs := []string{}
3333

34-
for i := 0; i < len(cmdArgs); i++ {
34+
i := 0
35+
for i < len(cmdArgs) {
3536
switch {
3637
case (cmdArgs[i] == FlagFormat || cmdArgs[i] == FlagFormatShort) && i+1 < len(cmdArgs):
3738
FormatOption = cmdArgs[i+1]
38-
i++
39+
i += 2
3940
case cmdArgs[i] == FlagServerLogs:
4041
ShowServerLogs = true
4142
i++
43+
case cmdArgs[i] == FlagAuthUser && i+1 < len(cmdArgs):
44+
AuthUser = cmdArgs[i+1]
45+
i += 2
46+
case cmdArgs[i] == FlagAuthHeader && i+1 < len(cmdArgs):
47+
AuthHeader = cmdArgs[i+1]
48+
i += 2
4249
default:
4350
parsedArgs = append(parsedArgs, cmdArgs[i])
51+
i++
4452
}
4553
}
4654

cmd/mcptools/commands/test_helpers.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ func (m *MockTransport) Close() error {
5454
return nil
5555
}
5656

57+
// GetSessionId returns an empty session ID for the mock transport.
58+
func (m *MockTransport) GetSessionId() string {
59+
return ""
60+
}
61+
5762
// setupMockClient creates a mock client with the given execute function and returns cleanup function.
5863
func setupMockClient(executeFunc func(method string, _ any) (map[string]any, error)) func() {
5964
// Save original function and restore later

cmd/mcptools/commands/utils.go

Lines changed: 100 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ package commands
33
import (
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.
3191
var 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++

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
1818
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
1919
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
2020
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
21-
github.com/mark3labs/mcp-go v0.23.1 h1:RzTzZ5kJ+HxwnutKA4rll8N/pKV6Wh5dhCmiJUu5S9I=
22-
github.com/mark3labs/mcp-go v0.23.1/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
2321
github.com/mark3labs/mcp-go v0.24.1 h1:YV+5X/+W4oBdERLWgiA1uR7AIvenlKJaa5V4hqufI7E=
2422
github.com/mark3labs/mcp-go v0.24.1/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
2523
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=

0 commit comments

Comments
 (0)