Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions cmd/src/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@ import (
"github.com/sourcegraph/sourcegraph/lib/errors"
)

var mcpFlagSet = flag.NewFlagSet("mcp", flag.ExitOnError)

func init() {
flagSet := flag.NewFlagSet("mcp", flag.ExitOnError)
commands = append(commands, &command{
flagSet: flagSet,
flagSet: mcpFlagSet,
handler: mcpMain,
})
}
func mcpMain(args []string) error {
fmt.Println("NOTE: This command is still experimental")
tools, err := mcp.LoadDefaultToolDefinitions()
apiClient := cfg.apiClient(nil, mcpFlagSet.Output())

ctx := context.Background()
tools, err := mcp.FetchToolDefinitions(ctx, apiClient)
if err != nil {
return err
}
Expand All @@ -44,10 +48,9 @@ func mcpMain(args []string) error {
fmt.Printf(" src mcp <tool-name> schema\n")
return nil
}

tool, ok := tools[subcmd]
if !ok {
return fmt.Errorf("tool definition for %q not found - run src mcp list-tools to see a list of available tools", subcmd)
return errors.Newf("tool definition for %q not found - run src mcp list-tools to see a list of available tools", subcmd)
}

flagArgs := args[1:] // skip subcommand name
Expand All @@ -68,7 +71,6 @@ func mcpMain(args []string) error {
return err
}

apiClient := cfg.apiClient(nil, flags.Output())
return handleMcpTool(context.Background(), apiClient, tool, vars)
}

Expand Down Expand Up @@ -101,7 +103,7 @@ func validateToolArgs(inputSchema mcp.SchemaObject, args []string, vars map[stri
}

func handleMcpTool(ctx context.Context, client api.Client, tool *mcp.ToolDef, vars map[string]any) error {
resp, err := mcp.DoToolRequest(ctx, client, tool, vars)
resp, err := mcp.DoToolCall(ctx, client, tool.RawName, vars)
if err != nil {
return err
}
Expand Down
7 changes: 0 additions & 7 deletions internal/mcp/mcp_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ import (
"github.com/sourcegraph/sourcegraph/lib/errors"
)

//go:embed mcp_tools.json
var mcpToolListJSON []byte

type ToolDef struct {
Name string
RawName string `json:"name"`
Expand Down Expand Up @@ -63,10 +60,6 @@ type decoder struct {
errors []error
}

func LoadDefaultToolDefinitions() (map[string]*ToolDef, error) {
return loadToolDefinitions(mcpToolListJSON)
}

func loadToolDefinitions(data []byte) (map[string]*ToolDef, error) {
defs := struct {
Tools []struct {
Expand Down
84 changes: 68 additions & 16 deletions internal/mcp/mcp_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"io"
"net/http"
"strings"

"github.com/sourcegraph/src-cli/internal/api"

Expand All @@ -14,25 +15,59 @@ import (

const McpURLPath = ".api/mcp/v1"

func DoToolRequest(ctx context.Context, client api.Client, tool *ToolDef, vars map[string]any) (*http.Response, error) {
func FetchToolDefinitions(ctx context.Context, client api.Client) (map[string]*ToolDef, error) {
resp, err := doJSONRPC(ctx, client, "tools/list", nil)
if err != nil {
return nil, errors.Wrap(err, "failed to list tools from mcp endpoint")
}
defer resp.Body.Close()

data, err := readSSEResponseData(resp)
if err != nil {
return nil, errors.Wrap(err, "failed to read list tools SSE response")
}

var rpcResp struct {
Result json.RawMessage `json:"result"`
Error *struct {
Code int `json:"code"`
Message string `json:"message"`
} `json:"error,omitempty"`
}
if err := json.Unmarshal(data, &rpcResp); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal JSON-RPC response")
}
if rpcResp.Error != nil {
return nil, errors.Newf("MCP tools/list failed: %d %s", rpcResp.Error.Code, rpcResp.Error.Message)
}

return loadToolDefinitions(rpcResp.Result)
}

func DoToolCall(ctx context.Context, client api.Client, tool string, vars map[string]any) (*http.Response, error) {
params := struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments"`
}{
Name: tool,
Arguments: vars,
}

return doJSONRPC(ctx, client, "tools/call", params)
}

func doJSONRPC(ctx context.Context, client api.Client, method string, params any) (*http.Response, error) {
jsonRPC := struct {
Version string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params any `json:"params"`
Params any `json:"params,omitempty"`
}{
Version: "2.0",
ID: 1,
Method: "tools/call",
Params: struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments"`
}{
Name: tool.RawName,
Arguments: vars,
},
Method: method,
Params: params,
}

buf := bytes.NewBuffer(nil)
data, err := json.Marshal(jsonRPC)
if err != nil {
Expand All @@ -45,9 +80,21 @@ func DoToolRequest(ctx context.Context, client api.Client, tool *ToolDef, vars m
return nil, err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "*/*")
req.Header.Add("Accept", "application/json, text/event-stream")

resp, err := client.Do(req)
if err != nil {
return nil, err
}

if resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
resp.Body.Close()
return nil, errors.Newf("MCP endpoint %s returned %d: %s",
McpURLPath, resp.StatusCode, strings.TrimSpace(string(body)))
}

return client.Do(req)
return resp, nil
}

func DecodeToolResponse(resp *http.Response) (map[string]json.RawMessage, error) {
Expand All @@ -61,16 +108,21 @@ func DecodeToolResponse(resp *http.Response) (map[string]json.RawMessage, error)
}

jsonRPCResp := struct {
Version string `json:"jsonrpc"`
ID int `json:"id"`
Result struct {
Result struct {
Content []json.RawMessage `json:"content"`
StructuredContent map[string]json.RawMessage `json:"structuredContent"`
} `json:"result"`
Error *struct {
Code int `json:"code"`
Message string `json:"message"`
} `json:"error,omitempty"`
}{}
if err := json.Unmarshal(data, &jsonRPCResp); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal MCP JSON-RPC response")
}
if jsonRPCResp.Error != nil {
return nil, errors.Newf("MCP tools/call failed: %d %s", jsonRPCResp.Error.Code, jsonRPCResp.Error.Message)
}

return jsonRPCResp.Result.StructuredContent, nil
}
Expand Down
21 changes: 0 additions & 21 deletions scripts/gen-mcp-tool-json.sh

This file was deleted.

Loading