| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- package agent
- import (
- "context"
- "encoding/json"
- "fmt"
- "github.com/sst/opencode/internal/config"
- "github.com/sst/opencode/internal/llm/tools"
- "github.com/sst/opencode/internal/permission"
- "github.com/sst/opencode/internal/version"
- "log/slog"
- "github.com/mark3labs/mcp-go/client"
- "github.com/mark3labs/mcp-go/mcp"
- )
- type mcpTool struct {
- mcpName string
- tool mcp.Tool
- mcpConfig config.MCPServer
- permissions permission.Service
- }
- type MCPClient interface {
- Initialize(
- ctx context.Context,
- request mcp.InitializeRequest,
- ) (*mcp.InitializeResult, error)
- ListTools(ctx context.Context, request mcp.ListToolsRequest) (*mcp.ListToolsResult, error)
- CallTool(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error)
- Close() error
- }
- func (b *mcpTool) Info() tools.ToolInfo {
- return tools.ToolInfo{
- Name: fmt.Sprintf("%s_%s", b.mcpName, b.tool.Name),
- Description: b.tool.Description,
- Parameters: b.tool.InputSchema.Properties,
- Required: b.tool.InputSchema.Required,
- }
- }
- func runTool(ctx context.Context, c MCPClient, toolName string, input string) (tools.ToolResponse, error) {
- defer c.Close()
- initRequest := mcp.InitializeRequest{}
- initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
- initRequest.Params.ClientInfo = mcp.Implementation{
- Name: "OpenCode",
- Version: version.Version,
- }
- _, err := c.Initialize(ctx, initRequest)
- if err != nil {
- return tools.NewTextErrorResponse(err.Error()), nil
- }
- toolRequest := mcp.CallToolRequest{}
- toolRequest.Params.Name = toolName
- var args map[string]any
- if err = json.Unmarshal([]byte(input), &args); err != nil {
- return tools.NewTextErrorResponse(fmt.Sprintf("error parsing parameters: %s", err)), nil
- }
- toolRequest.Params.Arguments = args
- result, err := c.CallTool(ctx, toolRequest)
- if err != nil {
- return tools.NewTextErrorResponse(err.Error()), nil
- }
- output := ""
- for _, v := range result.Content {
- if v, ok := v.(mcp.TextContent); ok {
- output = v.Text
- } else {
- output = fmt.Sprintf("%v", v)
- }
- }
- return tools.NewTextResponse(output), nil
- }
- func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolResponse, error) {
- sessionID, messageID := tools.GetContextValues(ctx)
- if sessionID == "" || messageID == "" {
- return tools.ToolResponse{}, fmt.Errorf("session ID and message ID are required for creating a new file")
- }
- permissionDescription := fmt.Sprintf("execute %s with the following parameters: %s", b.Info().Name, params.Input)
- p := b.permissions.Request(
- ctx,
- permission.CreatePermissionRequest{
- SessionID: sessionID,
- Path: config.WorkingDirectory(),
- ToolName: b.Info().Name,
- Action: "execute",
- Description: permissionDescription,
- Params: params.Input,
- },
- )
- if !p {
- return tools.NewTextErrorResponse("permission denied"), nil
- }
- switch b.mcpConfig.Type {
- case config.MCPStdio:
- c, err := client.NewStdioMCPClient(
- b.mcpConfig.Command,
- b.mcpConfig.Env,
- b.mcpConfig.Args...,
- )
- if err != nil {
- return tools.NewTextErrorResponse(err.Error()), nil
- }
- return runTool(ctx, c, b.tool.Name, params.Input)
- case config.MCPSse:
- c, err := client.NewSSEMCPClient(
- b.mcpConfig.URL,
- client.WithHeaders(b.mcpConfig.Headers),
- )
- if err != nil {
- return tools.NewTextErrorResponse(err.Error()), nil
- }
- return runTool(ctx, c, b.tool.Name, params.Input)
- }
- return tools.NewTextErrorResponse("invalid mcp type"), nil
- }
- func NewMcpTool(name string, tool mcp.Tool, permissions permission.Service, mcpConfig config.MCPServer) tools.BaseTool {
- return &mcpTool{
- mcpName: name,
- tool: tool,
- mcpConfig: mcpConfig,
- permissions: permissions,
- }
- }
- var mcpTools []tools.BaseTool
- func getTools(ctx context.Context, name string, m config.MCPServer, permissions permission.Service, c MCPClient) []tools.BaseTool {
- var stdioTools []tools.BaseTool
- initRequest := mcp.InitializeRequest{}
- initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
- initRequest.Params.ClientInfo = mcp.Implementation{
- Name: "OpenCode",
- Version: version.Version,
- }
- _, err := c.Initialize(ctx, initRequest)
- if err != nil {
- slog.Error("error initializing mcp client", "error", err)
- return stdioTools
- }
- toolsRequest := mcp.ListToolsRequest{}
- tools, err := c.ListTools(ctx, toolsRequest)
- if err != nil {
- slog.Error("error listing tools", "error", err)
- return stdioTools
- }
- for _, t := range tools.Tools {
- stdioTools = append(stdioTools, NewMcpTool(name, t, permissions, m))
- }
- defer c.Close()
- return stdioTools
- }
- func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.BaseTool {
- if len(mcpTools) > 0 {
- return mcpTools
- }
- for name, m := range config.Get().MCPServers {
- switch m.Type {
- case config.MCPStdio:
- c, err := client.NewStdioMCPClient(
- m.Command,
- m.Env,
- m.Args...,
- )
- if err != nil {
- slog.Error("error creating mcp client", "error", err)
- continue
- }
- mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c)...)
- case config.MCPSse:
- c, err := client.NewSSEMCPClient(
- m.URL,
- client.WithHeaders(m.Headers),
- )
- if err != nil {
- slog.Error("error creating mcp client", "error", err)
- continue
- }
- mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c)...)
- }
- }
- return mcpTools
- }
|