Skip to content

Commit

Permalink
🔍 Add debug mode and tracing capabilities to ShellToolProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
wesen committed Jan 26, 2025
1 parent a5cea58 commit 8c11dff
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 8 deletions.
11 changes: 10 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmd/mcp-server",
"args": ["start", "--transport", "sse", "--repositories", "examples/shell-commands", "--log-level", "debug"],
"args": ["start", "--transport", "sse", "--repositories", "examples/html-extract", "--log-level", "debug"],
"cwd": "${workspaceFolder}"
},
{
Expand Down Expand Up @@ -59,6 +59,15 @@
"--extract"
],
"cwd": "${workspaceFolder}"
},
{
"name": "Run HTML Extract - HN",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmd/mcp-server",
"args": ["run-command", "examples/html-extract/fetch-html.yaml", "--urls", "https://news.ycombinator.com/"],
"cwd": "${workspaceFolder}"
}
]
}
10 changes: 9 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -675,4 +675,12 @@ Added `fetch-html` command to simplify HTML content from URLs:
Added support for processing multiple files and URLs in the simplify-html command. The command now accepts:
- Multiple files via --files flag (including stdin with -)
- Multiple URLs via --urls flag
- Outputs results as a list of source and data pairs
- Outputs results as a list of source and data pairs

# Enhanced ShellToolProvider Debugging and Tracing

Added debug mode and tracing capabilities to ShellToolProvider for better debugging and audit capabilities:
- Added debug mode to log detailed information about tool calls and arguments
- Added tracing directory support to save input/output JSON files for each tool call
- Implemented functional options pattern for configuration
- Added timestamp-based file naming for trace files
2 changes: 1 addition & 1 deletion cmd/tools/simplify-html/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type SimplifyHTMLSettings struct {
func NewSimplifyHTMLCommand() (*SimplifyHTMLCommand, error) {
return &SimplifyHTMLCommand{
CommandDescription: cmds.NewCommandDescription(
"simplify-html",
"simplify",
cmds.WithShort("Simplify and minimize HTML documents"),
cmds.WithLong(`A tool to simplify and minimize HTML documents by removing unnecessary elements
and attributes, and shortening overly long text content. The output is provided
Expand Down
80 changes: 75 additions & 5 deletions pkg/cmds/shell-tool-provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"time"

"github.com/go-go-golems/glazed/pkg/cmds"
"github.com/go-go-golems/glazed/pkg/cmds/layers"
"github.com/go-go-golems/glazed/pkg/cmds/middlewares"
"github.com/go-go-golems/glazed/pkg/cmds/parameters"
"github.com/go-go-golems/go-go-mcp/pkg/protocol"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)

// ShellTool is a wrapper around a shell command that implements the Tool interface
Expand All @@ -21,15 +25,35 @@ type ShellTool struct {

// ShellToolProvider is a ToolProvider that exposes shell commands as tools
type ShellToolProvider struct {
commands map[string]*ShellTool
commands map[string]*ShellTool
debug bool
tracingDir string
}

type ShellToolProviderOption func(*ShellToolProvider)

func WithDebug(debug bool) ShellToolProviderOption {
return func(p *ShellToolProvider) {
p.debug = debug
}
}

func WithTracingDir(dir string) ShellToolProviderOption {
return func(p *ShellToolProvider) {
p.tracingDir = dir
}
}

// NewShellToolProvider creates a new ShellToolProvider with the given commands
func NewShellToolProvider(commands []cmds.Command) (*ShellToolProvider, error) {
func NewShellToolProvider(commands []cmds.Command, options ...ShellToolProviderOption) (*ShellToolProvider, error) {
provider := &ShellToolProvider{
commands: make(map[string]*ShellTool),
}

for _, option := range options {
option(provider)
}

for _, cmd := range commands {
if shellCmd, ok := cmd.(*ShellCommand); ok {
provider.commands[shellCmd.Description().Name] = &ShellTool{cmd: shellCmd}
Expand Down Expand Up @@ -57,7 +81,7 @@ func (p *ShellToolProvider) ListTools(cursor string) ([]protocol.Tool, string, e

tools = append(tools, protocol.Tool{
Name: desc.Name,
Description: desc.Short,
Description: desc.Short + "\n\n" + desc.Long,
InputSchema: schemaBytes,
})
}
Expand All @@ -67,11 +91,26 @@ func (p *ShellToolProvider) ListTools(cursor string) ([]protocol.Tool, string, e

// CallTool invokes a specific tool with the given arguments
func (p *ShellToolProvider) CallTool(ctx context.Context, name string, arguments map[string]interface{}) (*protocol.ToolResult, error) {
if p.debug {
log.Debug().
Str("name", name).
Interface("arguments", arguments).
Msg("calling tool with arguments")
}

tool, ok := p.commands[name]
if !ok {
return nil, fmt.Errorf("tool not found: %s", name)
}

if p.tracingDir != "" {
timestamp := time.Now().Format("2006-01-02T15-04-05.000")
inputFile := filepath.Join(p.tracingDir, fmt.Sprintf("%s-%s-input.json", name, timestamp))
if err := p.writeTraceFile(inputFile, arguments); err != nil {
log.Error().Err(err).Str("file", inputFile).Msg("failed to write input trace file")
}
}

// Get parameter layers from command description
parameterLayers := tool.cmd.Description().Layers

Expand Down Expand Up @@ -100,12 +139,43 @@ func (p *ShellToolProvider) CallTool(ctx context.Context, name string, arguments
buf := &strings.Builder{}

dataMap := parsedLayers.GetDataMap()

if p.debug {
log.Debug().Interface("dataMap", dataMap).Msg("processed arguments")
}

// Run the command with parsed parameters
err = tool.cmd.ExecuteCommand(ctx, dataMap, buf)
if err != nil {
return protocol.NewErrorToolResult(protocol.NewTextContent(err.Error())), nil
}

// Return the output as a text result
return protocol.NewToolResult(protocol.WithText(buf.String())), nil
result := protocol.NewToolResult(protocol.WithText(buf.String()))

if p.tracingDir != "" {
timestamp := time.Now().Format("2006-01-02T15-04-05.000")
outputFile := filepath.Join(p.tracingDir, fmt.Sprintf("%s-%s-output.json", name, timestamp))
if err := p.writeTraceFile(outputFile, result); err != nil {
log.Error().Err(err).Str("file", outputFile).Msg("failed to write output trace file")
}
}

return result, nil
}

func (p *ShellToolProvider) writeTraceFile(filename string, data interface{}) error {
if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
return fmt.Errorf("failed to create tracing directory: %w", err)
}

jsonData, err := json.MarshalIndent(data, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal data: %w", err)
}

if err := os.WriteFile(filename, jsonData, 0644); err != nil {
return fmt.Errorf("failed to write trace file: %w", err)
}

return nil
}

0 comments on commit 8c11dff

Please sign in to comment.