Skip to content

Commit d7c8949

Browse files
committed
make json rpc 2.0 call based on cli args
- basic sse resposne parsing - remove zero / nil arguments before making the request
1 parent c93e6a0 commit d7c8949

File tree

3 files changed

+123
-17
lines changed

3 files changed

+123
-17
lines changed

cmd/src/mcp.go

Lines changed: 101 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
package main
22

33
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
47
"flag"
58
"fmt"
9+
"io"
10+
"net/http"
611
"strings"
12+
13+
"github.com/sourcegraph/src-cli/internal/api"
14+
15+
"github.com/sourcegraph/sourcegraph/lib/errors"
716
)
817

18+
const McpPath = ".api/mcp/v1"
19+
920
func init() {
1021
flagSet := flag.NewFlagSet("mcp", flag.ExitOnError)
1122
commands = append(commands, &command{
@@ -33,37 +44,115 @@ func mcpMain(args []string) error {
3344
if !ok {
3445
return fmt.Errorf("tool definition for %q not found - run src mcp list-tools to see a list of available tools", subcmd)
3546
}
36-
return handleMcpTool(tool, args[1:])
37-
}
3847

39-
func handleMcpTool(tool *MCPToolDef, args []string) error {
40-
fs, vars, err := buildArgFlagSet(tool)
48+
flagArgs := args[1:] // skip subcommand name
49+
if len(args) > 1 && args[1] == "schema" {
50+
return printSchemas(tool)
51+
}
52+
53+
flags, vars, err := buildToolFlagSet(tool)
4154
if err != nil {
4255
return err
4356
}
57+
if err := flags.Parse(flagArgs); err != nil {
58+
return err
59+
}
60+
sanitizeFlagValues(vars)
4461

45-
if err := fs.Parse(args); err != nil {
62+
if err := validateToolArgs(tool.InputSchema, args, vars); err != nil {
4663
return err
4764
}
4865

49-
inputSchema := tool.InputSchema
66+
apiClient := cfg.apiClient(nil, flags.Output())
67+
return handleMcpTool(context.Background(), apiClient, tool, vars)
68+
}
5069

70+
func printSchemas(tool *MCPToolDef) error {
71+
input, err := json.MarshalIndent(tool.InputSchema, "", " ")
72+
if err != nil {
73+
return err
74+
}
75+
output, err := json.MarshalIndent(tool.OutputSchema, "", " ")
76+
if err != nil {
77+
return err
78+
}
79+
80+
fmt.Printf("Input:\n%v\nOutput:\n%v\n", string(input), string(output))
81+
return nil
82+
}
83+
84+
func validateToolArgs(inputSchema Schema, args []string, vars map[string]any) error {
5185
for _, reqName := range inputSchema.Required {
5286
if vars[reqName] == nil {
53-
return fmt.Errorf("no value provided for required flag --%s", reqName)
87+
return errors.Newf("no value provided for required flag --%s", reqName)
5488
}
5589
}
5690

5791
if len(args) < len(inputSchema.Required) {
58-
return fmt.Errorf("not enough arguments provided - the following flags are required:\n%s", strings.Join(inputSchema.Required, "\n"))
92+
return errors.Newf("not enough arguments provided - the following flags are required:\n%s", strings.Join(inputSchema.Required, "\n"))
5993
}
6094

61-
derefFlagValues(vars)
95+
return nil
96+
}
6297

63-
fmt.Println("Flags")
64-
for name, val := range vars {
65-
fmt.Printf("--%s=%v\n", name, val)
98+
func handleMcpTool(ctx context.Context, client api.Client, tool *MCPToolDef, vars map[string]any) error {
99+
jsonRPC := struct {
100+
Version string `json:"jsonrpc"`
101+
ID int `json:"id"`
102+
Method string `json:"method"`
103+
Params any `json:"params"`
104+
}{
105+
Version: "2.0",
106+
ID: 1,
107+
Method: "tools/call",
108+
Params: struct {
109+
Name string `json:"name"`
110+
Arguments map[string]any `json:"arguments"`
111+
}{
112+
Name: tool.RawName,
113+
Arguments: vars,
114+
},
66115
}
67116

117+
buf := bytes.NewBuffer(nil)
118+
data, err := json.Marshal(jsonRPC)
119+
if err != nil {
120+
return err
121+
}
122+
buf.Write(data)
123+
124+
req, err := client.NewHTTPRequest(ctx, http.MethodPost, McpPath, buf)
125+
if err != nil {
126+
return err
127+
}
128+
req.Header.Add("Content-Type", "application/json")
129+
req.Header.Add("Accept", "*/*")
130+
131+
resp, err := client.Do(req)
132+
if err != nil {
133+
return err
134+
}
135+
136+
data, err = io.ReadAll(resp.Body)
137+
if err != nil {
138+
return err
139+
}
140+
141+
jsonData, err := parseSSEResponse(data)
142+
if err != nil {
143+
return err
144+
}
145+
146+
fmt.Println(jsonData)
68147
return nil
69148
}
149+
150+
func parseSSEResponse(data []byte) ([]byte, error) {
151+
lines := bytes.SplitSeq(data, []byte("\n"))
152+
for line := range lines {
153+
if jsonData, ok := bytes.CutPrefix(line, []byte("data: ")); ok {
154+
return jsonData, nil
155+
}
156+
}
157+
return nil, errors.New("no data found in SSE response")
158+
}

cmd/src/mcp_args.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,37 @@ func (s *strSliceFlag) String() string {
2222
return strings.Join(s.vals, ",")
2323
}
2424

25-
func derefFlagValues(vars map[string]any) {
25+
func sanitizeFlagValues(vars map[string]any) {
2626
for k, v := range vars {
2727
rfl := reflect.ValueOf(v)
2828
if rfl.Kind() == reflect.Pointer {
2929
vv := rfl.Elem().Interface()
3030
if slice, ok := vv.(strSliceFlag); ok {
3131
vv = slice.vals
3232
}
33-
vars[k] = vv
33+
if isNil(vv) {
34+
delete(vars, k)
35+
} else {
36+
vars[k] = vv
37+
}
3438
}
3539
}
3640
}
3741

38-
func buildArgFlagSet(tool *MCPToolDef) (*flag.FlagSet, map[string]any, error) {
42+
func isNil(v any) bool {
43+
if v == nil {
44+
return true
45+
}
46+
rv := reflect.ValueOf(v)
47+
switch rv.Kind() {
48+
case reflect.Slice, reflect.Map, reflect.Pointer, reflect.Interface:
49+
return rv.IsNil()
50+
default:
51+
return false
52+
}
53+
}
54+
55+
func buildToolFlagSet(tool *MCPToolDef) (*flag.FlagSet, map[string]any, error) {
3956
fs := flag.NewFlagSet(tool.Name(), flag.ContinueOnError)
4057
flagVars := map[string]any{}
4158

cmd/src/mcp_args_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func TestFlagSetParse(t *testing.T) {
4949
t.Fatalf("failed to load tool json: %v", err)
5050
}
5151

52-
flagSet, vars, err := buildArgFlagSet(defs["sg_test_tool"])
52+
flagSet, vars, err := buildToolFlagSet(defs["sg_test_tool"])
5353
if err != nil {
5454
t.Fatalf("failed to build flagset from mcp tool definition: %v", err)
5555
}
@@ -63,7 +63,7 @@ func TestFlagSetParse(t *testing.T) {
6363
if err := flagSet.Parse(args); err != nil {
6464
t.Fatalf("flagset parsing failed: %v", err)
6565
}
66-
derefFlagValues(vars)
66+
sanitizeFlagValues(vars)
6767

6868
if v, ok := vars["repos"].([]string); ok {
6969
if len(v) != 2 {

0 commit comments

Comments
 (0)