mcp-tools.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. package agent
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "github.com/sst/opencode/internal/config"
  7. "github.com/sst/opencode/internal/llm/tools"
  8. "github.com/sst/opencode/internal/permission"
  9. "github.com/sst/opencode/internal/version"
  10. "log/slog"
  11. "github.com/mark3labs/mcp-go/client"
  12. "github.com/mark3labs/mcp-go/mcp"
  13. )
  14. type mcpTool struct {
  15. mcpName string
  16. tool mcp.Tool
  17. mcpConfig config.MCPServer
  18. permissions permission.Service
  19. }
  20. type MCPClient interface {
  21. Initialize(
  22. ctx context.Context,
  23. request mcp.InitializeRequest,
  24. ) (*mcp.InitializeResult, error)
  25. ListTools(ctx context.Context, request mcp.ListToolsRequest) (*mcp.ListToolsResult, error)
  26. CallTool(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error)
  27. Close() error
  28. }
  29. func (b *mcpTool) Info() tools.ToolInfo {
  30. return tools.ToolInfo{
  31. Name: fmt.Sprintf("%s_%s", b.mcpName, b.tool.Name),
  32. Description: b.tool.Description,
  33. Parameters: b.tool.InputSchema.Properties,
  34. Required: b.tool.InputSchema.Required,
  35. }
  36. }
  37. func runTool(ctx context.Context, c MCPClient, toolName string, input string) (tools.ToolResponse, error) {
  38. defer c.Close()
  39. initRequest := mcp.InitializeRequest{}
  40. initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
  41. initRequest.Params.ClientInfo = mcp.Implementation{
  42. Name: "OpenCode",
  43. Version: version.Version,
  44. }
  45. _, err := c.Initialize(ctx, initRequest)
  46. if err != nil {
  47. return tools.NewTextErrorResponse(err.Error()), nil
  48. }
  49. toolRequest := mcp.CallToolRequest{}
  50. toolRequest.Params.Name = toolName
  51. var args map[string]any
  52. if err = json.Unmarshal([]byte(input), &args); err != nil {
  53. return tools.NewTextErrorResponse(fmt.Sprintf("error parsing parameters: %s", err)), nil
  54. }
  55. toolRequest.Params.Arguments = args
  56. result, err := c.CallTool(ctx, toolRequest)
  57. if err != nil {
  58. return tools.NewTextErrorResponse(err.Error()), nil
  59. }
  60. output := ""
  61. for _, v := range result.Content {
  62. if v, ok := v.(mcp.TextContent); ok {
  63. output = v.Text
  64. } else {
  65. output = fmt.Sprintf("%v", v)
  66. }
  67. }
  68. return tools.NewTextResponse(output), nil
  69. }
  70. func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolResponse, error) {
  71. sessionID, messageID := tools.GetContextValues(ctx)
  72. if sessionID == "" || messageID == "" {
  73. return tools.ToolResponse{}, fmt.Errorf("session ID and message ID are required for creating a new file")
  74. }
  75. permissionDescription := fmt.Sprintf("execute %s with the following parameters: %s", b.Info().Name, params.Input)
  76. p := b.permissions.Request(
  77. ctx,
  78. permission.CreatePermissionRequest{
  79. SessionID: sessionID,
  80. Path: config.WorkingDirectory(),
  81. ToolName: b.Info().Name,
  82. Action: "execute",
  83. Description: permissionDescription,
  84. Params: params.Input,
  85. },
  86. )
  87. if !p {
  88. return tools.NewTextErrorResponse("permission denied"), nil
  89. }
  90. switch b.mcpConfig.Type {
  91. case config.MCPStdio:
  92. c, err := client.NewStdioMCPClient(
  93. b.mcpConfig.Command,
  94. b.mcpConfig.Env,
  95. b.mcpConfig.Args...,
  96. )
  97. if err != nil {
  98. return tools.NewTextErrorResponse(err.Error()), nil
  99. }
  100. return runTool(ctx, c, b.tool.Name, params.Input)
  101. case config.MCPSse:
  102. c, err := client.NewSSEMCPClient(
  103. b.mcpConfig.URL,
  104. client.WithHeaders(b.mcpConfig.Headers),
  105. )
  106. if err != nil {
  107. return tools.NewTextErrorResponse(err.Error()), nil
  108. }
  109. return runTool(ctx, c, b.tool.Name, params.Input)
  110. }
  111. return tools.NewTextErrorResponse("invalid mcp type"), nil
  112. }
  113. func NewMcpTool(name string, tool mcp.Tool, permissions permission.Service, mcpConfig config.MCPServer) tools.BaseTool {
  114. return &mcpTool{
  115. mcpName: name,
  116. tool: tool,
  117. mcpConfig: mcpConfig,
  118. permissions: permissions,
  119. }
  120. }
  121. var mcpTools []tools.BaseTool
  122. func getTools(ctx context.Context, name string, m config.MCPServer, permissions permission.Service, c MCPClient) []tools.BaseTool {
  123. var stdioTools []tools.BaseTool
  124. initRequest := mcp.InitializeRequest{}
  125. initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
  126. initRequest.Params.ClientInfo = mcp.Implementation{
  127. Name: "OpenCode",
  128. Version: version.Version,
  129. }
  130. _, err := c.Initialize(ctx, initRequest)
  131. if err != nil {
  132. slog.Error("error initializing mcp client", "error", err)
  133. return stdioTools
  134. }
  135. toolsRequest := mcp.ListToolsRequest{}
  136. tools, err := c.ListTools(ctx, toolsRequest)
  137. if err != nil {
  138. slog.Error("error listing tools", "error", err)
  139. return stdioTools
  140. }
  141. for _, t := range tools.Tools {
  142. stdioTools = append(stdioTools, NewMcpTool(name, t, permissions, m))
  143. }
  144. defer c.Close()
  145. return stdioTools
  146. }
  147. func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.BaseTool {
  148. if len(mcpTools) > 0 {
  149. return mcpTools
  150. }
  151. for name, m := range config.Get().MCPServers {
  152. switch m.Type {
  153. case config.MCPStdio:
  154. c, err := client.NewStdioMCPClient(
  155. m.Command,
  156. m.Env,
  157. m.Args...,
  158. )
  159. if err != nil {
  160. slog.Error("error creating mcp client", "error", err)
  161. continue
  162. }
  163. mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c)...)
  164. case config.MCPSse:
  165. c, err := client.NewSSEMCPClient(
  166. m.URL,
  167. client.WithHeaders(m.Headers),
  168. )
  169. if err != nil {
  170. slog.Error("error creating mcp client", "error", err)
  171. continue
  172. }
  173. mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c)...)
  174. }
  175. }
  176. return mcpTools
  177. }