Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,100 @@ mcp mock prompt greeting "Greeting template" "Hello {{name}}! Welcome to {{locat

When a client requests the prompt, it can provide values for these arguments which will be substituted in the response.

## Proxy Mode

The proxy mode allows you to register shell scripts as MCP tools and proxy MCP requests to them.
The scripts will receive tool parameters as environment variables.

### Registering a Tool

To register a shell script as an MCP tool:

```bash
mcp proxy tool add_operation "Adds a and b" "a:int,b:int" ./examples/add.sh
```

This registers a tool named `add_operation` with the description "Adds a and b" and parameters `a` and `b` of type `int`.
When the tool is called, the parameters will be passed as environment variables to the script.

Parameters are specified in the format `name:type,name:type,...` where `type` can be `string`, `int`, `float`, or `bool`.

### Starting the Proxy Server

To start a proxy server with the registered tools:

```bash
mcp proxy start
```

The server will run in stdio mode compatible with the MCP protocol and forward tool call requests to the registered shell scripts.

### Example Shell Scripts

#### Adding Numbers

```bash
#!/bin/bash

# Get the values from environment variables
if [ -z "$a" ] || [ -z "$b" ]; then
echo "Error: Missing required parameters 'a' or 'b'"
exit 1
fi

# Try to convert to integers
a_val=$(($a))
b_val=$(($b))

# Perform the addition
result=$(($a_val + $b_val))

# Return the result
echo "The sum of $a and $b is $result"
```

#### Customized Greeting

```bash
#!/bin/bash

# Get the values from environment variables
if [ -z "$name" ]; then
echo "Error: Missing required parameter 'name'"
exit 1
fi

# Set default values if not provided
if [ -z "$greeting" ]; then
greeting="Hello"
fi

if [ -z "$formal" ]; then
formal=false
fi

# Customize greeting based on formal flag
if [ "$formal" = "true" ]; then
title="Mr./Ms."
message="${greeting}, ${title} ${name}. How may I assist you today?"
else
message="${greeting}, ${name}! Nice to meet you!"
fi

# Return the greeting
echo "$message"
```

Register with:

```bash
mcp proxy tool greet "Greets a user" "name:string,greeting:string,formal:bool" ./examples/greet.sh
```

### Configuration

Tools are registered in `~/.mcpt/proxy_config.json`. The proxy server logs all requests and responses to `~/.mcpt/logs/proxy.log`.

## Examples

List tools from a filesystem server:
Expand Down Expand Up @@ -252,6 +346,47 @@ mcp mock tool file_reader "Reads files" \
resource docs://api "API Documentation" "# API Reference\n\nThis document describes the API."
```

Using the proxy mode with a simple shell script:

```bash
# 1. Create a simple shell script for addition
cat > add.sh << 'EOF'
#!/bin/bash
# Get values from environment variables
if [ -z "$a" ] || [ -z "$b" ]; then
echo "Error: Missing required parameters 'a' or 'b'"
exit 1
fi
result=$(($a + $b))
echo "The sum of $a and $b is $result"
EOF

# 2. Make it executable
chmod +x add.sh

# 3. Register it as an MCP tool
mcp proxy tool add_numbers "Adds two numbers" "a:int,b:int" ./add.sh

# 4. In one terminal, start the proxy server
mcp proxy start

# 5. In another terminal, you can call it as an MCP tool
mcp call add_numbers --params '{"a":5,"b":3}' --format pretty
```

Tailing the logs to debug your proxy or mock server:

```bash
# For the mock server logs
tail -f ~/.mcpt/logs/mock.log

# For the proxy server logs
tail -f ~/.mcpt/logs/proxy.log

# To watch all logs in real-time (on macOS/Linux)
find ~/.mcpt/logs -name "*.log" -exec tail -f {} \;
```

## Contributing

We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md)
Expand Down
138 changes: 138 additions & 0 deletions cmd/mcptools/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@ import (
"encoding/json"
"errors"
"fmt"
"log"
"os"
"path/filepath"
"strings"

"github.com/f/mcptools/pkg/client"
"github.com/f/mcptools/pkg/jsonutils"
"github.com/f/mcptools/pkg/mock"
"github.com/f/mcptools/pkg/proxy"
"github.com/peterh/liner"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// version information placeholders.
Expand Down Expand Up @@ -61,6 +64,7 @@ func main() {
newReadResourceCmd(),
newShellCmd(),
newMockCmd(),
proxyCmd(),
)

if err := rootCmd.Execute(); err != nil {
Expand Down Expand Up @@ -939,3 +943,137 @@ Example:

return cmd
}

func proxyCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "proxy",
Short: "Proxy MCP tool requests to shell scripts",
Long: `Proxy MCP tool requests to shell scripts.

This command allows you to register shell scripts as MCP tools and proxy MCP requests to them.
The scripts will receive tool parameters as environment variables.

Examples:
# Register a shell script as an MCP tool
mcp proxy tool add_operation "Adds a and b" "a:int,b:int" ./add.sh

# Start a proxy server with the registered tools
mcp proxy start`,
}

cmd.AddCommand(proxyToolCmd())
cmd.AddCommand(proxyStartCmd())

return cmd
}

func proxyToolCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "tool NAME DESCRIPTION PARAMETERS SCRIPT_PATH",
Short: "Register a shell script as an MCP tool",
Long: `Register a shell script as an MCP tool.

The PARAMETERS argument should be a comma-separated list of "name:type" pairs.
Supported types: string, int, float, bool

Example:
mcp proxy tool add_operation "Adds a and b" "a:int,b:int" ./add.sh`,
Args: cobra.ExactArgs(4),
Run: func(_ *cobra.Command, args []string) {
name := args[0]
description := args[1]
parameters := args[2]
scriptPath := args[3]

// Initialize config
viper.SetConfigName("proxy_config")
viper.SetConfigType("json")
viper.AddConfigPath("$HOME/.mcpt")

// Create config directory if it doesn't exist
configDir := os.ExpandEnv("$HOME/.mcpt")
if err := os.MkdirAll(configDir, 0o750); err != nil {
log.Fatalf("Error creating config directory: %v", err)
}

// Load existing config if it exists
var config map[string]map[string]string
var configFileNotFound viper.ConfigFileNotFoundError
err := viper.ReadInConfig()
if err != nil {
if errors.As(err, &configFileNotFound) {
// Config file not found, create a new one
config = make(map[string]map[string]string)
} else {
log.Fatalf("Error reading config: %v", err)
}
} else {
// Config file found, unmarshal it
config = make(map[string]map[string]string)
unmarshalErr := viper.Unmarshal(&config)
if unmarshalErr != nil {
log.Fatalf("Error unmarshaling config: %v", unmarshalErr)
}
}

// Add or update tool config
config[name] = map[string]string{
"description": description,
"parameters": parameters,
"script": scriptPath,
}

// Save config
configPath := os.ExpandEnv("$HOME/.mcpt/proxy_config.json")
configJSON, err := json.MarshalIndent(config, "", " ")
if err != nil {
log.Fatalf("Error marshaling config: %v", err)
}

writeErr := os.WriteFile(configPath, configJSON, 0o600)
if writeErr != nil {
log.Fatalf("Error writing config: %v", writeErr)
}

fmt.Printf("Registered tool '%s' with script '%s'\n", name, scriptPath)
},
}

return cmd
}

func proxyStartCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "start",
Short: "Start a proxy server with registered tools",
Long: `Start a proxy server that forwards MCP tool requests to shell scripts.

The server reads tool configurations from $HOME/.mcpt/proxy_config.json.

Example:
mcp proxy start`,
Run: func(_ *cobra.Command, _ []string) {
// Load tool configurations
viper.SetConfigName("proxy_config")
viper.SetConfigType("json")
viper.AddConfigPath("$HOME/.mcpt")

if err := viper.ReadInConfig(); err != nil {
log.Fatalf("Error reading config: %v", err)
}

var config map[string]map[string]string
if err := viper.Unmarshal(&config); err != nil {
log.Fatalf("Error unmarshaling config: %v", err)
}

// Run proxy server
fmt.Println("Starting proxy server...")
if err := proxy.RunProxyServer(config); err != nil {
log.Fatalf("Error running proxy server: %v", err)
}
},
}

return cmd
}
17 changes: 17 additions & 0 deletions examples/add.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

# Get the values from environment variables
if [ -z "$a" ] || [ -z "$b" ]; then
echo "Error: Missing required parameters 'a' or 'b'"
exit 1
fi

# Try to convert to integers
a_val=$(($a))
b_val=$(($b))

# Perform the addition
result=$(($a_val + $b_val))

# Return the result
echo "The sum of $a and $b is $result"
27 changes: 27 additions & 0 deletions examples/greet.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash

# Get the values from environment variables
if [ -z "$name" ]; then
echo "Error: Missing required parameter 'name'"
exit 1
fi

# Set default values if not provided
if [ -z "$greeting" ]; then
greeting="Hello"
fi

if [ -z "$formal" ]; then
formal=false
fi

# Customize greeting based on formal flag
if [ "$formal" = "true" ]; then
title="Mr./Ms."
message="${greeting}, ${title} ${name}. How may I assist you today?"
else
message="${greeting}, ${name}! Nice to meet you!"
fi

# Return the greeting
echo "$message"
12 changes: 12 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,28 @@ require (
)

require (
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/spf13/viper v1.20.1 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
mvdan.cc/gofumpt v0.7.0 // indirect
)
Loading