@@ -7,120 +7,39 @@ import (
11− "github.com/charmbracelet/crush/internal/agent/tools"
12 "github.com/charmbracelet/crush/internal/csync"
10 "github.com/charmbracelet/crush/internal/csync"
13− "github.com/charmbracelet/crush/internal/permission"
14 "github.com/modelcontextprotocol/go-sdk/mcp"
11 "github.com/modelcontextprotocol/go-sdk/mcp"
18 allTools = csync.NewMap[string, *Tool]()
17 allTools = csync.NewMap[string, *Tool]()
19 client2Tools = csync.NewMap[string, []*Tool]()
18 client2Tools = csync.NewMap[string, []*Tool]()
22−// GetMCPTools returns all available MCP tools.
21+// GetTools returns all available MCP tools.
23−func GetMCPTools() iter.Seq[*Tool] {
22+func GetTools() iter.Seq2[string, *Tool] {
24− return allTools.Seq()
23+ return allTools.Seq2()
30− permissions permission.Service
32− providerOptions fantasy.ProviderOptions
35−func (m *Tool) SetProviderOptions(opts fantasy.ProviderOptions) {
36− m.providerOptions = opts
39−func (m *Tool) ProviderOptions() fantasy.ProviderOptions {
40− return m.providerOptions
43−func (m *Tool) Name() string {
26+// RunTool runs an MCP tool with the given input parameters.
44− return fmt.Sprintf("mcp_%s_%s", m.mcpName, m.tool.Name)
47−func (m *Tool) MCP() string {
51−func (m *Tool) MCPToolName() string {
55−func (m *Tool) Info() fantasy.ToolInfo {
56− parameters := make(map[string]any)
57− required := make([]string, 0)
59− if input, ok := m.tool.InputSchema.(map[string]any); ok {
60− if props, ok := input["properties"].(map[string]any); ok {
63− if req, ok := input["required"].([]any); ok {
64− // Convert []any -> []string when elements are strings
65− for _, v := range req {
66− if s, ok := v.(string); ok {
67− required = append(required, s)
70− } else if reqStr, ok := input["required"].([]string); ok {
71− // Handle case where it's already []string
76− return fantasy.ToolInfo{
77− Name: fmt.Sprintf("mcp_%s_%s", m.mcpName, m.tool.Name),
78− Description: m.tool.Description,
79− Parameters: parameters,
84−func (m *Tool) Run(ctx context.Context, params fantasy.ToolCall) (fantasy.ToolResponse, error) {
85− sessionID := tools.GetSessionFromContext(ctx)
87− return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for creating a new file")
89− permissionDescription := fmt.Sprintf("execute %s with the following parameters:", m.Info().Name)
90− p := m.permissions.Request(
91− permission.CreatePermissionRequest{
92− SessionID: sessionID,
93− ToolCallID: params.ID,
95− ToolName: m.Info().Name,
97− Description: permissionDescription,
98− Params: params.Input,
102− return fantasy.ToolResponse{}, permission.ErrorPermissionDenied
105− return runTool(ctx, m.mcpName, m.tool.Name, params.Input)
108−func runTool(ctx context.Context, name, toolName string, input string) (fantasy.ToolResponse, error) {
27+func RunTool(ctx context.Context, name, toolName string, input string) (string, error) {
109 var args map[string]any
28 var args map[string]any
110 if err := json.Unmarshal([]byte(input), &args); err != nil {
29 if err := json.Unmarshal([]byte(input), &args); err != nil {
111− return fantasy.NewTextErrorResponse(fmt.Sprintf("error parsing parameters: %s", err)), nil
30+ return "", fmt.Errorf("error parsing parameters: %s", err)
114 c, err := getOrRenewClient(ctx, name)
33 c, err := getOrRenewClient(ctx, name)
115 if err != nil {
34 if err != nil {
116− return fantasy.NewTextErrorResponse(err.Error()), nil
35+ return "", err
118 result, err := c.CallTool(ctx, &mcp.CallToolParams{
37 result, err := c.CallTool(ctx, &mcp.CallToolParams{
119 Name: toolName,
38 Name: toolName,
120 Arguments: args,
39 Arguments: args,
122 if err != nil {
41 if err != nil {
123− return fantasy.NewTextErrorResponse(err.Error()), nil
42+ return "", err
126 output := make([]string, 0, len(result.Content))
45 output := make([]string, 0, len(result.Content))
@@ -131,27 +50,18 @@ func runTool(ctx context.Context, name, toolName string, input string) (fantasy.
131 output = append(output, fmt.Sprintf("%v", v))
50 output = append(output, fmt.Sprintf("%v", v))
134− return fantasy.NewTextResponse(strings.Join(output, "\n")), nil
53+ return strings.Join(output, "\n"), nil
137−func getTools(ctx context.Context, name string, permissions permission.Service, c *mcp.ClientSession, workingDir string) ([]*Tool, error) {
56+func getTools(ctx context.Context, session *mcp.ClientSession) ([]*Tool, error) {
138− if c.InitializeResult().Capabilities.Tools == nil {
57+ if session.InitializeResult().Capabilities.Tools == nil {
139 return nil, nil
58 return nil, nil
141− result, err := c.ListTools(ctx, &mcp.ListToolsParams{})
60+ result, err := session.ListTools(ctx, &mcp.ListToolsParams{})
142 if err != nil {
61 if err != nil {
143 return nil, err
62 return nil, err
145− mcpTools := make([]*Tool, 0, len(result.Tools))
146− for _, tool := range result.Tools {
147− mcpTools = append(mcpTools, &Tool{
150− permissions: permissions,
151− workingDir: workingDir,
154− return mcpTools, nil
64+ return result.Tools, nil
157// updateTools updates the global mcpTools and mcpClient2Tools maps
67// updateTools updates the global mcpTools and mcpClient2Tools maps
@@ -161,9 +71,9 @@ func updateTools(mcpName string, tools []*Tool) {
162 client2Tools.Set(mcpName, tools)
72 client2Tools.Set(mcpName, tools)
164− for _, tools := range client2Tools.Seq2() {
74+ for name, tools := range client2Tools.Seq2() {
165 for _, t := range tools {
75 for _, t := range tools {
166− allTools.Set(t.Name(), t)
76+ allTools.Set(name, t)