mcp-tools.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package tools
  2. import (
  3. "context"
  4. "fmt"
  5. "charm.land/fantasy"
  6. "github.com/charmbracelet/crush/internal/agent/tools/mcp"
  7. "github.com/charmbracelet/crush/internal/permission"
  8. )
  9. // GetMCPTools gets all the currently available MCP tools.
  10. func GetMCPTools(permissions permission.Service, wd string) []*Tool {
  11. var result []*Tool
  12. for mcpName, tools := range mcp.Tools() {
  13. for _, tool := range tools {
  14. result = append(result, &Tool{
  15. mcpName: mcpName,
  16. tool: tool,
  17. permissions: permissions,
  18. workingDir: wd,
  19. })
  20. }
  21. }
  22. return result
  23. }
  24. // Tool is a tool from a MCP.
  25. type Tool struct {
  26. mcpName string
  27. tool *mcp.Tool
  28. permissions permission.Service
  29. workingDir string
  30. providerOptions fantasy.ProviderOptions
  31. }
  32. func (m *Tool) SetProviderOptions(opts fantasy.ProviderOptions) {
  33. m.providerOptions = opts
  34. }
  35. func (m *Tool) ProviderOptions() fantasy.ProviderOptions {
  36. return m.providerOptions
  37. }
  38. func (m *Tool) Name() string {
  39. return fmt.Sprintf("mcp_%s_%s", m.mcpName, m.tool.Name)
  40. }
  41. func (m *Tool) MCP() string {
  42. return m.mcpName
  43. }
  44. func (m *Tool) MCPToolName() string {
  45. return m.tool.Name
  46. }
  47. func (m *Tool) Info() fantasy.ToolInfo {
  48. parameters := make(map[string]any)
  49. required := make([]string, 0)
  50. if input, ok := m.tool.InputSchema.(map[string]any); ok {
  51. if props, ok := input["properties"].(map[string]any); ok {
  52. parameters = props
  53. }
  54. if req, ok := input["required"].([]any); ok {
  55. // Convert []any -> []string when elements are strings
  56. for _, v := range req {
  57. if s, ok := v.(string); ok {
  58. required = append(required, s)
  59. }
  60. }
  61. } else if reqStr, ok := input["required"].([]string); ok {
  62. // Handle case where it's already []string
  63. required = reqStr
  64. }
  65. }
  66. return fantasy.ToolInfo{
  67. Name: m.Name(),
  68. Description: m.tool.Description,
  69. Parameters: parameters,
  70. Required: required,
  71. }
  72. }
  73. func (m *Tool) Run(ctx context.Context, params fantasy.ToolCall) (fantasy.ToolResponse, error) {
  74. sessionID := GetSessionFromContext(ctx)
  75. if sessionID == "" {
  76. return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for creating a new file")
  77. }
  78. permissionDescription := fmt.Sprintf("execute %s with the following parameters:", m.Info().Name)
  79. p := m.permissions.Request(
  80. permission.CreatePermissionRequest{
  81. SessionID: sessionID,
  82. ToolCallID: params.ID,
  83. Path: m.workingDir,
  84. ToolName: m.Info().Name,
  85. Action: "execute",
  86. Description: permissionDescription,
  87. Params: params.Input,
  88. },
  89. )
  90. if !p {
  91. return fantasy.ToolResponse{}, permission.ErrorPermissionDenied
  92. }
  93. content, err := mcp.RunTool(ctx, m.mcpName, m.tool.Name, params.Input)
  94. if err != nil {
  95. return fantasy.NewTextErrorResponse(err.Error()), nil
  96. }
  97. return fantasy.NewTextResponse(content), nil
  98. }