Skip to content

Commit 7493fb0

Browse files
authored
Merge branch 'main' into wb/stderr-notice
2 parents 2104f64 + 2f0b82f commit 7493fb0

File tree

3 files changed

+56
-39
lines changed

3 files changed

+56
-39
lines changed

cmd/src/mcp.go

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,21 @@ var mcpFlagSet = flag.NewFlagSet("mcp", flag.ExitOnError)
1717

1818
func init() {
1919
commands = append(commands, &command{
20-
flagSet: mcpFlagSet,
21-
handler: mcpMain,
20+
flagSet: mcpFlagSet,
21+
handler: mcpMain,
22+
usageFunc: mcpUsage,
2223
})
2324
}
25+
26+
func mcpUsage() {
27+
fmt.Println("The 'mcp' command exposes MCP tools as subcommands for agents to use.")
28+
fmt.Println("\nUSAGE:")
29+
fmt.Println(" src mcp list-tools List available tools")
30+
fmt.Println(" src mcp <tool-name> schema View the input/output schema of a tool")
31+
fmt.Println(" src mcp <tool-name> <flags> Invoke a tool with the given flags")
32+
fmt.Println(" src mcp <tool-name> -h List the available flags of a tool")
33+
}
34+
2435
func mcpMain(args []string) error {
2536
apiClient := cfg.apiClient(nil, mcpFlagSet.Output())
2637

@@ -30,21 +41,21 @@ func mcpMain(args []string) error {
3041
return err
3142
}
3243

44+
if len(args) == 0 {
45+
mcpUsage()
46+
return nil
47+
}
48+
3349
subcmd := args[0]
3450
if subcmd == "list-tools" {
3551
fmt.Println("The following tools are available:")
3652
for name := range tools {
37-
fmt.Printf(" %s\n", name)
53+
fmt.Printf(" %s\n", name)
3854
}
3955
fmt.Println("\nUSAGE:")
40-
fmt.Printf(" • Invoke a tool\n")
41-
fmt.Printf(" src mcp <tool-name> <flags>\n")
42-
fmt.Printf("\n • View the Input / Output Schema of a tool\n")
43-
fmt.Printf(" src mcp <tool-name> schema\n")
44-
fmt.Printf("\n • List the available flags of a tool\n")
45-
fmt.Printf(" src mcp <tool-name> -h\n")
46-
fmt.Printf("\n • View the Input / Output Schema of a tool\n")
47-
fmt.Printf(" src mcp <tool-name> schema\n")
56+
fmt.Println(" src mcp <tool-name> schema View the input/output schema of a tool")
57+
fmt.Println(" src mcp <tool-name> <flags> Invoke a tool with the given flags")
58+
fmt.Println(" src mcp <tool-name> -h List the available flags of a tool")
4859
return nil
4960
}
5061
tool, ok := tools[subcmd]
@@ -64,7 +75,7 @@ func mcpMain(args []string) error {
6475
if err := flags.Parse(flagArgs); err != nil {
6576
return err
6677
}
67-
mcp.DerefFlagValues(vars)
78+
mcp.DerefFlagValues(flags, vars)
6879

6980
if err := validateToolArgs(tool.InputSchema, args, vars); err != nil {
7081
return err

internal/mcp/mcp_args.go

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package mcp
22

33
import (
4+
"encoding/json"
45
"flag"
56
"fmt"
67
"reflect"
@@ -16,6 +17,16 @@ type strSliceFlag struct {
1617
}
1718

1819
func (s *strSliceFlag) Set(v string) error {
20+
// The MCP Array properties accept JSON arrays so, if we get a value starting with "["
21+
// it's probably a JSON array
22+
if strings.HasPrefix(v, "[") {
23+
var arr []string
24+
if err := json.Unmarshal([]byte(v), &arr); err == nil {
25+
s.vals = append(s.vals, arr...)
26+
return nil
27+
}
28+
}
29+
// Otherwise treat as a single value
1930
s.vals = append(s.vals, v)
2031
return nil
2132
}
@@ -24,36 +35,28 @@ func (s *strSliceFlag) String() string {
2435
return strings.Join(s.vals, ",")
2536
}
2637

27-
func DerefFlagValues(vars map[string]any) {
38+
func DerefFlagValues(fs *flag.FlagSet, vars map[string]any) {
39+
setFlags := make(map[string]bool)
40+
fs.Visit(func(f *flag.Flag) {
41+
setFlags[f.Name] = true
42+
})
43+
2844
for k, v := range vars {
45+
if !setFlags[k] {
46+
delete(vars, k)
47+
continue
48+
}
2949
rfl := reflect.ValueOf(v)
3050
if rfl.Kind() == reflect.Pointer {
3151
vv := rfl.Elem().Interface()
3252
if slice, ok := vv.(strSliceFlag); ok {
3353
vv = slice.vals
3454
}
35-
if isNil(vv) {
36-
delete(vars, k)
37-
} else {
38-
vars[k] = vv
39-
}
55+
vars[k] = vv
4056
}
4157
}
4258
}
4359

44-
func isNil(v any) bool {
45-
if v == nil {
46-
return true
47-
}
48-
rv := reflect.ValueOf(v)
49-
switch rv.Kind() {
50-
case reflect.Slice, reflect.Map, reflect.Pointer, reflect.Interface:
51-
return rv.IsNil()
52-
default:
53-
return false
54-
}
55-
}
56-
5760
func BuildArgFlagSet(tool *ToolDef) (*flag.FlagSet, map[string]any, error) {
5861
if tool == nil {
5962
return nil, nil, errors.New("cannot build flagset on nil Tool Definition")

internal/mcp/mcp_args_test.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package mcp
22

33
import (
4+
"slices"
45
"testing"
56
)
67

@@ -58,20 +59,22 @@ func TestFlagSetParse(t *testing.T) {
5859
t.Fatalf("vars from buildArgFlagSet should not be empty")
5960
}
6061

61-
args := []string{"-repos=A", "-repos=B", "-count=10", "-boolFlag", "-tag=testTag"}
62+
args := []string{"-repos=A", "-repos=B", `-repos=["repo1", "repo2"]`, "-count=10", "-boolFlag", "-tag=testTag"}
6263

6364
if err := flagSet.Parse(args); err != nil {
6465
t.Fatalf("flagset parsing failed: %v", err)
6566
}
66-
DerefFlagValues(vars)
67+
DerefFlagValues(flagSet, vars)
6768

68-
if v, ok := vars["repos"].([]string); ok {
69-
if len(v) != 2 {
70-
t.Fatalf("expected flag 'repos' values to have length %d but got %d", 2, len(v))
71-
}
72-
} else {
73-
t.Fatalf("expected flag 'repos' to have type of []string but got %T", v)
69+
expectedRepos := []string{"A", "B", "repo1", "repo2"}
70+
actualRepos, ok := vars["repos"].([]string)
71+
if !ok {
72+
t.Fatalf("failed to cast repos to []string, got %T", actualRepos)
7473
}
74+
if !slices.Equal(expectedRepos, actualRepos) {
75+
t.Fatalf("expected repos %v, got %v", expectedRepos, vars["repos"])
76+
}
77+
7578
if v, ok := vars["tag"].(string); ok {
7679
if v != "testTag" {
7780
t.Fatalf("expected flag 'tag' values to have value %q but got %q", "testTag", v)

0 commit comments

Comments
 (0)