Skip to content

Commit ad05f8f

Browse files
committed
initial
1 parent d743d3b commit ad05f8f

File tree

8 files changed

+884
-21
lines changed

8 files changed

+884
-21
lines changed

README.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
- [Server Modes](#server-modes)
3535
- [Mock Server Mode](#mock-server-mode)
3636
- [Proxy Mode](#proxy-mode)
37+
- [Guard Mode](#guard-mode)
3738
- [Examples](#examples)
3839
- [Basic Usage](#basic-usage)
3940
- [Script Integration](#script-integration)
@@ -564,6 +565,84 @@ mcp proxy tool count_lines "Counts lines in a file" "file:string" -e "wc -l < \"
564565
- The proxy server logs all requests and responses to `~/.mcpt/logs/proxy.log`
565566
- Use `--unregister` to remove a tool from the configuration
566567

568+
### Guard Mode
569+
570+
The guard mode allows you to restrict access to specific tools, prompts, and resources based on pattern matching. This is useful for security purposes when:
571+
572+
- Restricting potentially dangerous operations (file writes, deletions, etc.)
573+
- Limiting the capabilities of AI assistants or applications
574+
- Providing read-only access to sensitive systems
575+
- Creating sandboxed environments for testing or demonstrations
576+
577+
```bash
578+
# Allow only file reading operations, deny file modifications
579+
mcp guard --allow tools:read_* --deny tools:write_*,create_*,delete_* npx -y @modelcontextprotocol/server-filesystem ~
580+
581+
# Permit only a single specific tool
582+
mcp guard --allow tools:search_files npx -y @modelcontextprotocol/server-filesystem ~
583+
584+
# Restrict by both tool type and prompt type
585+
mcp guard --allow tools:read_*,prompts:system_* --deny tools:execute_* npx -y @modelcontextprotocol/server-filesystem ~
586+
```
587+
588+
#### How It Works
589+
590+
The guard command works by:
591+
1. Creating a proxy that sits between the client and the MCP server
592+
2. Intercepting and filtering all requests to `tools/list`, `prompts/list`, and `resources/list`
593+
3. Preventing calls to tools, prompts, or resources that don't match the allowed patterns
594+
4. Passing through all other requests and responses unchanged
595+
596+
#### Pattern Matching
597+
598+
Patterns use simple glob syntax with `*` as a wildcard:
599+
600+
- `tools:read_*` - Matches all tools starting with "read_"
601+
- `tools:*file*` - Matches any tool with "file" in the name
602+
- `prompts:system_*` - Matches all prompts starting with "system_"
603+
604+
For each entity type, you can specify:
605+
- `--allow pattern1,pattern2,...` - Only allow entities matching these patterns
606+
- `--deny pattern1,pattern2,...` - Remove entities matching these patterns
607+
608+
If no allow patterns are specified, all entities are allowed by default (except those matching deny patterns).
609+
610+
#### Application Integration
611+
612+
You can use the guard command to secure MCP configurations in applications. For example, to restrict a file system server to only allow read operations, change:
613+
614+
```json
615+
"filesystem": {
616+
"command": "npx",
617+
"args": [
618+
"-y",
619+
"@modelcontextprotocol/server-filesystem",
620+
"/Users/fka/Desktop"
621+
]
622+
}
623+
```
624+
625+
To:
626+
627+
```json
628+
"filesystem": {
629+
"command": "mcp",
630+
"args": [
631+
"guard", "--deny", "tools:write_*,create_*,move_*,delete_*",
632+
"npx", "-y", "@modelcontextprotocol/server-filesystem",
633+
"/Users/fka/Desktop"
634+
]
635+
}
636+
```
637+
638+
This provides a read-only view of the filesystem by preventing any modification operations.
639+
640+
#### Logging
641+
642+
- Guard operations are logged to `~/.mcpt/logs/guard.log`
643+
- The log includes all requests, responses, and filtering decisions
644+
- Use `tail -f ~/.mcpt/logs/guard.log` to monitor activity in real-time
645+
567646
## Examples
568647

569648
### Basic Usage
@@ -580,6 +659,16 @@ Call a tool with pretty JSON output:
580659
mcp call read_file --params '{"path":"README.md"}' --format pretty npx -y @modelcontextprotocol/server-filesystem ~
581660
```
582661

662+
Use the guard mode to filter available tools:
663+
664+
```bash
665+
# Only allow file search functionality
666+
mcp guard --allow tools:search_files npx -y @modelcontextprotocol/server-filesystem ~
667+
668+
# Create a read-only environment
669+
mcp guard --deny tools:write_*,delete_*,create_*,move_* npx -y @modelcontextprotocol/server-filesystem ~
670+
```
671+
583672
### Script Integration
584673

585674
Using the proxy mode with a simple shell script:

cmd/mcptools/commands/guard.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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

Comments
 (0)