|
| 1 | +package commands |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "os" |
| 6 | + "strings" |
| 7 | + |
| 8 | + "github.com/f/mcptools/pkg/guard" |
| 9 | + "github.com/spf13/cobra" |
| 10 | +) |
| 11 | + |
| 12 | +// Guard flags. |
| 13 | +const ( |
| 14 | + FlagAllow = "--allow" |
| 15 | + FlagAllowShort = "-a" |
| 16 | + FlagDeny = "--deny" |
| 17 | + FlagDenyShort = "-d" |
| 18 | +) |
| 19 | + |
| 20 | +var entityTypes = []string{ |
| 21 | + EntityTypeTool, |
| 22 | + EntityTypePrompt, |
| 23 | + EntityTypeRes, |
| 24 | +} |
| 25 | + |
| 26 | +// GuardCmd creates the guard command to filter tools, prompts, and resources. |
| 27 | +func GuardCmd() *cobra.Command { |
| 28 | + return &cobra.Command{ |
| 29 | + Use: "guard [--allow type:pattern] [--deny type:pattern] command args...", |
| 30 | + Short: "Filter tools, prompts, and resources using allow and deny patterns", |
| 31 | + Long: `Filter tools, prompts, and resources using allow and deny patterns. |
| 32 | +
|
| 33 | +Examples: |
| 34 | + mcp guard --allow tools:read_* --deny edit_*,write_*,create_* npx run @modelcontextprotocol/server-filesystem ~ |
| 35 | + mcp guard --allow prompts:system_* --deny tools:execute_* npx run @modelcontextprotocol/server-filesystem ~ |
| 36 | + |
| 37 | +Patterns can include wildcards: |
| 38 | + * matches any sequence of characters |
| 39 | + |
| 40 | +Entity types: |
| 41 | + tools: filter available tools |
| 42 | + prompts: filter available prompts |
| 43 | + resource: filter available resources`, |
| 44 | + DisableFlagParsing: true, |
| 45 | + SilenceUsage: true, |
| 46 | + Run: func(thisCmd *cobra.Command, args []string) { |
| 47 | + if len(args) == 1 && (args[0] == FlagHelp || args[0] == FlagHelpShort) { |
| 48 | + _ = thisCmd.Help() |
| 49 | + return |
| 50 | + } |
| 51 | + |
| 52 | + // Process and extract the allow and deny patterns |
| 53 | + allowPatterns, denyPatterns, cmdArgs := extractPatterns(args) |
| 54 | + |
| 55 | + // Process regular flags (format) |
| 56 | + parsedArgs := ProcessFlags(cmdArgs) |
| 57 | + |
| 58 | + // Print filtering info |
| 59 | + fmt.Fprintf(os.Stderr, "Guard filtering configuration:\n") |
| 60 | + for _, entityType := range entityTypes { |
| 61 | + if len(allowPatterns[entityType]) > 0 { |
| 62 | + fmt.Fprintf(os.Stderr, "Allowing %s matching: %s\n", entityType, strings.Join(allowPatterns[entityType], ", ")) |
| 63 | + } |
| 64 | + if len(denyPatterns[entityType]) > 0 { |
| 65 | + fmt.Fprintf(os.Stderr, "Denying %s matching: %s\n", entityType, strings.Join(denyPatterns[entityType], ", ")) |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + // Verify we have a command to run |
| 70 | + if len(parsedArgs) == 0 { |
| 71 | + fmt.Fprintf(os.Stderr, "Error: command to execute is required\n") |
| 72 | + fmt.Fprintf(os.Stderr, "Example: mcp guard --allow tools:read_* npx -y @modelcontextprotocol/server-filesystem ~\n") |
| 73 | + os.Exit(1) |
| 74 | + } |
| 75 | + |
| 76 | + // Map our entity types to the guard proxy entity types |
| 77 | + guardAllowPatterns := map[string][]string{ |
| 78 | + "tool": allowPatterns[EntityTypeTool], |
| 79 | + "prompt": allowPatterns[EntityTypePrompt], |
| 80 | + "resource": allowPatterns[EntityTypeRes], |
| 81 | + } |
| 82 | + guardDenyPatterns := map[string][]string{ |
| 83 | + "tool": denyPatterns[EntityTypeTool], |
| 84 | + "prompt": denyPatterns[EntityTypePrompt], |
| 85 | + "resource": denyPatterns[EntityTypeRes], |
| 86 | + } |
| 87 | + |
| 88 | + // Run the guard proxy with the filtered environment |
| 89 | + fmt.Fprintf(os.Stderr, "Running command with filtered environment: %s\n", strings.Join(parsedArgs, " ")) |
| 90 | + if err := guard.RunFilterServer(guardAllowPatterns, guardDenyPatterns, parsedArgs); err != nil { |
| 91 | + fmt.Fprintf(os.Stderr, "Error: %v\n", err) |
| 92 | + os.Exit(1) |
| 93 | + } |
| 94 | + }, |
| 95 | + } |
| 96 | +} |
| 97 | + |
| 98 | +// extractPatterns processes arguments to extract allow and deny patterns. |
| 99 | +func extractPatterns(args []string) (map[string][]string, map[string][]string, []string) { |
| 100 | + allowPatterns := make(map[string][]string) |
| 101 | + denyPatterns := make(map[string][]string) |
| 102 | + |
| 103 | + // Initialize maps for all entity types |
| 104 | + for _, entityType := range entityTypes { |
| 105 | + allowPatterns[entityType] = []string{} |
| 106 | + denyPatterns[entityType] = []string{} |
| 107 | + } |
| 108 | + |
| 109 | + cmdArgs := []string{} |
| 110 | + i := 0 |
| 111 | + for i < len(args) { |
| 112 | + switch { |
| 113 | + case (args[i] == FlagAllow || args[i] == FlagAllowShort) && i+1 < len(args): |
| 114 | + // Process --allow flag |
| 115 | + patternsStr := args[i+1] |
| 116 | + processPatternString(patternsStr, allowPatterns) |
| 117 | + i += 2 |
| 118 | + case (args[i] == FlagDeny || args[i] == FlagDenyShort) && i+1 < len(args): |
| 119 | + // Process --deny flag |
| 120 | + patternsStr := args[i+1] |
| 121 | + processPatternString(patternsStr, denyPatterns) |
| 122 | + i += 2 |
| 123 | + default: |
| 124 | + // Not a flag we recognize, pass it along |
| 125 | + cmdArgs = append(cmdArgs, args[i]) |
| 126 | + i++ |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + return allowPatterns, denyPatterns, cmdArgs |
| 131 | +} |
| 132 | + |
| 133 | +// processPatternString processes a comma-separated pattern string. |
| 134 | +func processPatternString(patternsStr string, patternMap map[string][]string) { |
| 135 | + patterns := strings.Split(patternsStr, ",") |
| 136 | + |
| 137 | + for _, pattern := range patterns { |
| 138 | + pattern = strings.TrimSpace(pattern) |
| 139 | + if pattern == "" { |
| 140 | + continue |
| 141 | + } |
| 142 | + |
| 143 | + parts := strings.SplitN(pattern, ":", 2) |
| 144 | + if len(parts) != 2 { |
| 145 | + // If no type specified, assume it's a tool pattern |
| 146 | + patternMap[EntityTypeTool] = append(patternMap[EntityTypeTool], pattern) |
| 147 | + continue |
| 148 | + } |
| 149 | + |
| 150 | + entityType := parts[0] |
| 151 | + patternValue := parts[1] |
| 152 | + |
| 153 | + // Map entity type to known types |
| 154 | + switch entityType { |
| 155 | + case "tool", "tools": |
| 156 | + patternMap[EntityTypeTool] = append(patternMap[EntityTypeTool], patternValue) |
| 157 | + case "prompt", "prompts": |
| 158 | + patternMap[EntityTypePrompt] = append(patternMap[EntityTypePrompt], patternValue) |
| 159 | + case "resource", "resources", "res": |
| 160 | + patternMap[EntityTypeRes] = append(patternMap[EntityTypeRes], patternValue) |
| 161 | + default: |
| 162 | + // Unknown entity type, treat as tool pattern |
| 163 | + patternMap[EntityTypeTool] = append(patternMap[EntityTypeTool], pattern) |
| 164 | + } |
| 165 | + } |
| 166 | +} |
0 commit comments