Skip to content

Commit ca86fe0

Browse files
authored
feat(mcp): add mcp list-tools to list available tool calls (#1213)
* add mcp list-tools to list available tool calls * set variable name for embedded json * make default LoadToolDefinitions and unexported loadToolDefinitions * use internal/mcp * improve list-tools output * change name to RawName and have method for Name() that is normalized * remove Name() method and use method name field * feat(mcp): build flagset from mcp input schema to parse args (#1215) - parse input schema to build flagset for mcp command * review feedback - errors.Wrapf - strings.TrimPrefix
1 parent 89494c8 commit ca86fe0

File tree

9 files changed

+454
-41
lines changed

9 files changed

+454
-41
lines changed

cmd/src/main.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ The commands are:
6363
search search for results on Sourcegraph
6464
search-jobs manages search jobs
6565
serve-git serves your local git repositories over HTTP for Sourcegraph to pull
66-
tool exposes tools for AI agents to interact with Sourcegraph (EXPERIMENTAL)
6766
users,user manages users
6867
codeowners manages code ownership information
6968
version display and compare the src-cli version against the recommended version for your instance

cmd/src/mcp.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
7+
"github.com/sourcegraph/src-cli/internal/mcp"
8+
)
9+
10+
func init() {
11+
flagSet := flag.NewFlagSet("mcp", flag.ExitOnError)
12+
commands = append(commands, &command{
13+
flagSet: flagSet,
14+
handler: mcpMain,
15+
})
16+
}
17+
func mcpMain(args []string) error {
18+
fmt.Println("NOTE: This command is still experimental")
19+
tools, err := mcp.LoadToolDefinitions()
20+
if err != nil {
21+
return err
22+
}
23+
24+
subcmd := args[0]
25+
if subcmd == "list-tools" {
26+
fmt.Println("The following tools are available:")
27+
for name := range tools {
28+
fmt.Printf(" • %s\n", name)
29+
}
30+
fmt.Println("\nUSAGE:")
31+
fmt.Printf(" • Invoke a tool\n")
32+
fmt.Printf(" src mcp <tool-name> <flags>\n")
33+
fmt.Printf("\n • View the Input / Output Schema of a tool\n")
34+
fmt.Printf(" src mcp <tool-name> schema\n")
35+
fmt.Printf("\n • List the available flags of a tool\n")
36+
fmt.Printf(" src mcp <tool-name> -h\n")
37+
fmt.Printf("\n • View the Input / Output Schema of a tool\n")
38+
fmt.Printf(" src mcp <tool-name> schema\n")
39+
return nil
40+
}
41+
42+
tool, ok := tools[subcmd]
43+
if !ok {
44+
return fmt.Errorf("tool definition for %q not found - run src mcp list-tools to see a list of available tools", subcmd)
45+
}
46+
47+
flagArgs := args[1:] // skip subcommand name
48+
if len(args) > 1 && args[1] == "schema" {
49+
return printSchemas(tool)
50+
}
51+
52+
flags, vars, err := mcp.BuildArgFlagSet(tool)
53+
if err != nil {
54+
return err
55+
}
56+
if err := flags.Parse(flagArgs); err != nil {
57+
return err
58+
}
59+
mcp.DerefFlagValues(vars)
60+
61+
if err := validateToolArgs(tool.InputSchema, args, vars); err != nil {
62+
return err
63+
}
64+
65+
apiClient := cfg.apiClient(nil, flags.Output())
66+
return handleMcpTool(context.Background(), apiClient, tool, vars)
67+
}
68+
69+
func printSchemas(tool *mcp.ToolDef) error {
70+
input, err := json.MarshalIndent(tool.InputSchema, "", " ")
71+
if err != nil {
72+
return err
73+
}
74+
output, err := json.MarshalIndent(tool.OutputSchema, "", " ")
75+
if err != nil {
76+
return err
77+
}
78+
79+
fmt.Printf("Input:\n%v\nOutput:\n%v\n", string(input), string(output))
80+
return nil
81+
}
82+
83+
func validateToolArgs(inputSchema mcp.SchemaObject, args []string, vars map[string]any) error {
84+
for _, reqName := range inputSchema.Required {
85+
if vars[reqName] == nil {
86+
return errors.Newf("no value provided for required flag --%s", reqName)
87+
}
88+
}
89+
90+
if len(args) < len(inputSchema.Required) {
91+
return errors.Newf("not enough arguments provided - the following flags are required:\n%s", strings.Join(inputSchema.Required, "\n"))
92+
}
93+
94+
return nil
95+
}
96+
97+
func handleMcpTool(ctx context.Context, client api.Client, tool *mcp.ToolDef, vars map[string]any) error {
98+
resp, err := mcp.DoToolRequest(ctx, client, tool, vars)
99+
if err != nil {
100+
return err
101+
}
102+
103+
result, err := mcp.DecodeToolResponse(resp)
104+
if err != nil {
105+
return err
106+
}
107+
defer resp.Body.Close()
108+
109+
output, err := json.MarshalIndent(result, "", " ")
110+
if err != nil {
111+
return err
112+
}
113+
fmt.Println(string(output))
114+
return nil
115+
}

go.mod

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ require (
2929
github.com/sourcegraph/scip v0.6.1
3030
github.com/sourcegraph/sourcegraph/lib v0.0.0-20240709083501-1af563b61442
3131
github.com/stretchr/testify v1.11.1
32-
golang.org/x/net v0.46.0
32+
golang.org/x/net v0.47.0
3333
golang.org/x/sync v0.18.0
3434
google.golang.org/api v0.256.0
3535
google.golang.org/protobuf v1.36.10
@@ -67,15 +67,15 @@ require (
6767
github.com/distribution/reference v0.6.0 // indirect
6868
github.com/docker/cli v24.0.4+incompatible // indirect
6969
github.com/docker/distribution v2.8.2+incompatible // indirect
70-
github.com/docker/docker v25.0.6+incompatible // indirect
70+
github.com/docker/docker v28.0.0+incompatible // indirect
7171
github.com/docker/docker-credential-helpers v0.8.0 // indirect
7272
github.com/docker/go-connections v0.4.0 // indirect
7373
github.com/docker/go-units v0.5.0 // indirect
7474
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
7575
github.com/felixge/fgprof v0.9.3 // indirect
7676
github.com/felixge/httpsnoop v1.0.4 // indirect
7777
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
78-
github.com/go-chi/chi/v5 v5.0.10 // indirect
78+
github.com/go-chi/chi/v5 v5.2.2 // indirect
7979
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
8080
github.com/go-logr/stdr v1.2.2 // indirect
8181
github.com/gofrs/uuid/v5 v5.0.0 // indirect
@@ -91,11 +91,12 @@ require (
9191
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
9292
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
9393
github.com/mitchellh/go-homedir v1.1.0 // indirect
94+
github.com/moby/docker-image-spec v1.3.1 // indirect
9495
github.com/morikuni/aec v1.0.0 // indirect
9596
github.com/opencontainers/go-digest v1.0.0 // indirect
9697
github.com/opencontainers/image-spec v1.1.0-rc4 // indirect
9798
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
98-
github.com/rs/cors v1.9.0 // indirect
99+
github.com/rs/cors v1.11.0 // indirect
99100
github.com/sirupsen/logrus v1.9.3 // indirect
100101
github.com/sourcegraph/beaut v0.0.0-20240611013027-627e4c25335a // indirect
101102
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
@@ -116,7 +117,7 @@ require (
116117
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
117118
go.opentelemetry.io/otel/trace v1.38.0 // indirect
118119
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
119-
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 // indirect
120+
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 // indirect
120121
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
121122
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect
122123
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
@@ -216,14 +217,14 @@ require (
216217
go.uber.org/atomic v1.11.0 // indirect
217218
go.uber.org/multierr v1.11.0 // indirect
218219
go.uber.org/zap v1.24.0 // indirect
219-
golang.org/x/crypto v0.43.0 // indirect
220-
golang.org/x/mod v0.28.0 // indirect
220+
golang.org/x/crypto v0.45.0 // indirect
221+
golang.org/x/mod v0.29.0 // indirect
221222
golang.org/x/oauth2 v0.33.0 // indirect
222223
golang.org/x/sys v0.38.0 // indirect
223224
golang.org/x/term v0.37.0 // indirect
224-
golang.org/x/text v0.30.0 // indirect
225+
golang.org/x/text v0.31.0 // indirect
225226
golang.org/x/time v0.14.0 // indirect
226-
golang.org/x/tools v0.37.0 // indirect
227+
golang.org/x/tools v0.38.0 // indirect
227228
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
228229
google.golang.org/grpc v1.76.0 // indirect
229230
gopkg.in/inf.v0 v0.9.1 // indirect; direct

go.sum

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ github.com/docker/cli v24.0.4+incompatible h1:Y3bYF9ekNTm2VFz5U/0BlMdJy73D+Y1iAA
154154
github.com/docker/cli v24.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
155155
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
156156
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
157-
github.com/docker/docker v25.0.6+incompatible h1:5cPwbwriIcsua2REJe8HqQV+6WlWc1byg2QSXzBxBGg=
158-
github.com/docker/docker v25.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
157+
github.com/docker/docker v28.0.0+incompatible h1:Olh0KS820sJ7nPsBKChVhk5pzqcwDR15fumfAd/p9hM=
158+
github.com/docker/docker v28.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
159159
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
160160
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
161161
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
@@ -188,8 +188,8 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv
188188
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
189189
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
190190
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
191-
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
192-
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
191+
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
192+
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
193193
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
194194
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
195195
github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
@@ -344,6 +344,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
344344
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
345345
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
346346
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
347+
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
348+
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
347349
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
348350
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
349351
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -402,8 +404,8 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
402404
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
403405
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
404406
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
405-
github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
406-
github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
407+
github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
408+
github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
407409
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
408410
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
409411
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
@@ -513,25 +515,25 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
513515
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
514516
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
515517
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
516-
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
517-
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
518+
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
519+
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
518520
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
519521
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
520522
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
521523
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
522524
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
523525
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
524-
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
525-
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
526+
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
527+
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
526528
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
527529
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
528530
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
529531
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
530532
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
531533
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
532534
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
533-
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
534-
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
535+
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
536+
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
535537
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
536538
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
537539
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -563,8 +565,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
563565
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
564566
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
565567
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
566-
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 h1:dHQOQddU4YHS5gY33/6klKjq7Gp3WwMyOXGNp5nzRj8=
567-
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053/go.mod h1:+nZKN+XVh4LCiA9DV3ywrzN4gumyCnKjau3NGb9SGoE=
568+
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=
569+
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
568570
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
569571
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
570572
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
@@ -574,8 +576,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
574576
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
575577
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
576578
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
577-
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
578-
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
579+
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
580+
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
579581
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
580582
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
581583
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -586,8 +588,8 @@ golang.org/x/tools v0.0.0-20200624163319-25775e59acb7/go.mod h1:EkVYQZoAsY45+roY
586588
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
587589
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
588590
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
589-
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
590-
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
591+
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
592+
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
591593
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
592594
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
593595
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

internal/mcp/mcp_args.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package mcp
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"reflect"
7+
"strings"
8+
9+
"github.com/sourcegraph/sourcegraph/lib/errors"
10+
)
11+
12+
var _ flag.Value = (*strSliceFlag)(nil)
13+
14+
type strSliceFlag struct {
15+
vals []string
16+
}
17+
18+
func (s *strSliceFlag) Set(v string) error {
19+
s.vals = append(s.vals, v)
20+
return nil
21+
}
22+
23+
func (s *strSliceFlag) String() string {
24+
return strings.Join(s.vals, ",")
25+
}
26+
27+
func DerefFlagValues(vars map[string]any) {
28+
for k, v := range vars {
29+
rfl := reflect.ValueOf(v)
30+
if rfl.Kind() == reflect.Pointer {
31+
vv := rfl.Elem().Interface()
32+
if slice, ok := vv.(strSliceFlag); ok {
33+
vv = slice.vals
34+
}
35+
if isNil(vv) {
36+
delete(vars, k)
37+
} else {
38+
vars[k] = vv
39+
}
40+
}
41+
}
42+
}
43+
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+
57+
func BuildArgFlagSet(tool *ToolDef) (*flag.FlagSet, map[string]any, error) {
58+
if tool == nil {
59+
return nil, nil, errors.New("cannot build flagset on nil Tool Definition")
60+
}
61+
fs := flag.NewFlagSet(tool.Name, flag.ContinueOnError)
62+
flagVars := map[string]any{}
63+
64+
for name, pVal := range tool.InputSchema.Properties {
65+
switch pv := pVal.(type) {
66+
case *SchemaPrimitive:
67+
switch pv.Type {
68+
case "integer":
69+
dst := fs.Int(name, 0, pv.Description)
70+
flagVars[name] = dst
71+
72+
case "boolean":
73+
dst := fs.Bool(name, false, pv.Description)
74+
flagVars[name] = dst
75+
case "string":
76+
dst := fs.String(name, "", pv.Description)
77+
flagVars[name] = dst
78+
default:
79+
return nil, nil, fmt.Errorf("unknown schema primitive kind %q", pv.Type)
80+
81+
}
82+
case *SchemaArray:
83+
strSlice := new(strSliceFlag)
84+
fs.Var(strSlice, name, pv.Description)
85+
flagVars[name] = strSlice
86+
case *SchemaObject:
87+
// TODO(burmudar): we can support SchemaObject as part of stdin echo '{ stuff }' | sg mcp commit-search
88+
// not supported yet
89+
// Also support sg mcp commit-search --json '{ stuff }'
90+
}
91+
}
92+
93+
return fs, flagVars, nil
94+
}

0 commit comments

Comments
 (0)