mirror of
https://github.com/charmbracelet/crush.git
synced 2026-05-30 18:47:33 +00:00
103 lines
2.9 KiB
Go
103 lines
2.9 KiB
Go
package tools
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
_ "embed"
|
|
"fmt"
|
|
"log/slog"
|
|
"strings"
|
|
|
|
"charm.land/fantasy"
|
|
"github.com/charmbracelet/crush/internal/agent/tools/mcp"
|
|
"github.com/charmbracelet/crush/internal/config"
|
|
"github.com/charmbracelet/crush/internal/filepathext"
|
|
"github.com/charmbracelet/crush/internal/permission"
|
|
)
|
|
|
|
type ReadMCPResourceParams struct {
|
|
MCPName string `json:"mcp_name" description:"The MCP server name"`
|
|
URI string `json:"uri" description:"The resource URI to read"`
|
|
}
|
|
|
|
type ReadMCPResourcePermissionsParams struct {
|
|
MCPName string `json:"mcp_name"`
|
|
URI string `json:"uri"`
|
|
}
|
|
|
|
const ReadMCPResourceToolName = "read_mcp_resource"
|
|
|
|
//go:embed read_mcp_resource.md
|
|
var readMCPResourceDescription string
|
|
|
|
func NewReadMCPResourceTool(cfg *config.ConfigStore, permissions permission.Service) fantasy.AgentTool {
|
|
return fantasy.NewParallelAgentTool(
|
|
ReadMCPResourceToolName,
|
|
readMCPResourceDescription,
|
|
func(ctx context.Context, params ReadMCPResourceParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
|
|
params.MCPName = strings.TrimSpace(params.MCPName)
|
|
params.URI = strings.TrimSpace(params.URI)
|
|
if params.MCPName == "" {
|
|
return fantasy.NewTextErrorResponse("mcp_name parameter is required"), nil
|
|
}
|
|
if params.URI == "" {
|
|
return fantasy.NewTextErrorResponse("uri parameter is required"), nil
|
|
}
|
|
|
|
sessionID := GetSessionFromContext(ctx)
|
|
if sessionID == "" {
|
|
return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for reading MCP resources")
|
|
}
|
|
|
|
relPath := filepathext.SmartJoin(cfg.WorkingDir(), cmp.Or(params.URI, "mcp-resource"))
|
|
p, err := permissions.Request(ctx,
|
|
permission.CreatePermissionRequest{
|
|
SessionID: sessionID,
|
|
Path: relPath,
|
|
ToolCallID: call.ID,
|
|
ToolName: ReadMCPResourceToolName,
|
|
Action: "read",
|
|
Description: fmt.Sprintf("Read MCP resource from %s", params.MCPName),
|
|
Params: ReadMCPResourcePermissionsParams(params),
|
|
},
|
|
)
|
|
if err != nil {
|
|
return fantasy.ToolResponse{}, err
|
|
}
|
|
if !p {
|
|
return NewPermissionDeniedResponse(), nil
|
|
}
|
|
|
|
contents, err := mcp.ReadResource(ctx, cfg, params.MCPName, params.URI)
|
|
if err != nil {
|
|
return fantasy.NewTextErrorResponse(err.Error()), nil
|
|
}
|
|
if len(contents) == 0 {
|
|
return fantasy.NewTextResponse(""), nil
|
|
}
|
|
|
|
var textParts []string
|
|
for _, content := range contents {
|
|
if content == nil {
|
|
continue
|
|
}
|
|
if content.Text != "" {
|
|
textParts = append(textParts, content.Text)
|
|
continue
|
|
}
|
|
if len(content.Blob) > 0 {
|
|
textParts = append(textParts, string(content.Blob))
|
|
continue
|
|
}
|
|
slog.Debug("MCP resource content missing text/blob", "uri", content.URI)
|
|
}
|
|
|
|
if len(textParts) == 0 {
|
|
return fantasy.NewTextResponse(""), nil
|
|
}
|
|
|
|
return fantasy.NewTextResponse(strings.Join(textParts, "\n")), nil
|
|
},
|
|
)
|
|
}
|