Skip to content

Commit c88351b

Browse files
committed
Merge remote-tracking branch 'upstream/master'
2 parents 1993554 + 4a29eb5 commit c88351b

File tree

3 files changed

+77
-9
lines changed

3 files changed

+77
-9
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,9 @@ mcp proxy tool add_operation "Adds a and b" "a:int,b:int" ./examples/add.sh
556556
# Register an inline command as an MCP tool
557557
mcp proxy tool add_operation "Adds a and b" "a:int,b:int" -e 'echo "total is $a + $b = $(($a+$b))"'
558558

559+
# Register an inline command as an MCP tool with optional parameter
560+
mcpt proxy tool add_operation "Adds a and b with optional result msg" "a:int,b:int,[msg:string]" -e 'echo "$msg$a + $b = $(($a+$b))"'
561+
559562
# Unregister a tool
560563
mcp proxy tool --unregister add_operation
561564

@@ -581,6 +584,8 @@ This new format clearly shows what parameters each tool accepts, making it easie
581584
2. Start the proxy server, which implements the MCP protocol
582585
3. When a tool is called, parameters are passed as environment variables to the script/command
583586
4. The script/command's output is returned as the tool response
587+
5. If the script's output is a base64-encoded PNG image (prefixed with `data:image/png;base64,`), it is returned as an [ImageContent](https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#image-content) object.
588+
584589

585590
#### Example Scripts and Commands
586591

@@ -599,6 +604,16 @@ result=$(($a + $b))
599604
echo "The sum of $a and $b is $result"
600605
```
601606

607+
**Generating a QR Code**
608+
609+
This example requires a tool like `qrencode` to be installed.
610+
611+
```bash
612+
# Register a tool to generate a QR code
613+
mcp proxy tool qrcode "Generates a QR code" "text:string" \
614+
-e 'echo -e "data:image/png;base64,$(qrencode -t png -o - "$text" | base64 -w 0)"'
615+
```
616+
602617
**Inline Command Example:**
603618

604619
```bash

cmd/mcptools/commands/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
var Version = "dev"
1212

1313
// getHomeDirectory returns the user's home directory
14-
// Tries HOME first, then falls back to USERPROFILE for Windows
14+
// Tries HOME first, then falls back to USERPROFILE for Windows.
1515
func getHomeDirectory() string {
1616
homeDir := os.Getenv("HOME")
1717
if homeDir == "" {

pkg/proxy/proxy.go

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ import (
1616

1717
// Parameter represents a tool parameter with a name and type.
1818
type Parameter struct {
19-
Name string
20-
Type string
19+
Name string
20+
Type string
21+
Required bool
2122
}
2223

2324
// Tool represents a proxy tool that executes a shell script or command.
@@ -157,6 +158,7 @@ func (s *Server) AddTool(name, description, paramStr, scriptPath string, command
157158
}
158159

159160
// parseParameters parses a comma-separated parameter string in the format "name:type,name:type".
161+
// If a parameter is wrapped in square brackets like [name:type], it's considered optional.
160162
func parseParameters(paramStr string) ([]Parameter, error) {
161163
if paramStr == "" {
162164
return []Parameter{}, nil
@@ -166,6 +168,17 @@ func parseParameters(paramStr string) ([]Parameter, error) {
166168
parameters := make([]Parameter, 0, len(params))
167169

168170
for _, param := range params {
171+
param = strings.TrimSpace(param)
172+
required := true
173+
174+
// Check if parameter is optional (wrapped in square brackets)
175+
if strings.HasPrefix(param, "[") && strings.HasSuffix(param, "]") {
176+
// Remove the brackets
177+
param = strings.TrimPrefix(param, "[")
178+
param = strings.TrimSuffix(param, "]")
179+
required = false
180+
}
181+
169182
parts := strings.Split(strings.TrimSpace(param), ":")
170183
if len(parts) != 2 {
171184
return nil, fmt.Errorf("invalid parameter format: %s, expected name:type", param)
@@ -189,8 +202,9 @@ func parseParameters(paramStr string) ([]Parameter, error) {
189202
}
190203

191204
parameters = append(parameters, Parameter{
192-
Name: name,
193-
Type: normalizedType,
205+
Name: name,
206+
Type: normalizedType,
207+
Required: required,
194208
})
195209
}
196210

@@ -286,7 +300,11 @@ func (s *Server) GetToolSchema(toolName string) (map[string]interface{}, error)
286300
}
287301

288302
properties[param.Name] = paramSchema
289-
required = append(required, param.Name)
303+
304+
// Only add the parameter to required list if it's marked as required
305+
if param.Required {
306+
required = append(required, param.Name)
307+
}
290308
}
291309

292310
schema := map[string]interface{}{
@@ -435,7 +453,11 @@ func (s *Server) handleToolsList() map[string]interface{} {
435453
}
436454

437455
properties[param.Name] = paramSchema
438-
required = append(required, param.Name)
456+
457+
// Only add to required list if the parameter is required
458+
if param.Required {
459+
required = append(required, param.Name)
460+
}
439461
}
440462

441463
schema := map[string]interface{}{
@@ -471,7 +493,7 @@ func (s *Server) handleToolCall(params map[string]interface{}) (map[string]inter
471493
return nil, fmt.Errorf("'name' parameter must be a string")
472494
}
473495

474-
_, exists := s.tools[name]
496+
tool, exists := s.tools[name]
475497
if !exists {
476498
return nil, fmt.Errorf("tool not found: %s", name)
477499
}
@@ -487,6 +509,15 @@ func (s *Server) handleToolCall(params map[string]interface{}) (map[string]inter
487509
return nil, fmt.Errorf("'arguments' parameter must be an object")
488510
}
489511

512+
// Check for required parameters
513+
for _, param := range tool.Parameters {
514+
if param.Required {
515+
if _, exists := arguments[param.Name]; !exists {
516+
return nil, fmt.Errorf("missing required parameter: %s", param.Name)
517+
}
518+
}
519+
}
520+
490521
// Log the input parameters
491522
s.logJSON("Tool input", arguments)
492523

@@ -501,6 +532,24 @@ func (s *Server) handleToolCall(params map[string]interface{}) (map[string]inter
501532
s.log(fmt.Sprintf("Script output: %s", output))
502533

503534
// Return the output in the correct format for the MCP protocol
535+
// Check if the output is a base64-encoded PNG image
536+
// https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#image-content
537+
if strings.HasPrefix(output, "data:image/png;base64,") {
538+
base64Data := strings.TrimPrefix(output, "data:image/png;base64,")
539+
return map[string]interface{}{
540+
"content": []map[string]interface{}{
541+
{
542+
"type": "text",
543+
"text": "generated PNG image",
544+
},
545+
{
546+
"type": "image",
547+
"data": base64Data,
548+
"mimeType": "image/png",
549+
},
550+
},
551+
}, nil
552+
}
504553
return map[string]interface{}{
505554
"content": []map[string]interface{}{
506555
{
@@ -587,7 +636,11 @@ func RunProxyServer(toolConfigs map[string]map[string]string) error {
587636
if i > 0 {
588637
paramStr += ", "
589638
}
590-
paramStr += param.Name + ":" + param.Type
639+
if param.Required {
640+
paramStr += param.Name + ":" + param.Type
641+
} else {
642+
paramStr += "[" + param.Name + ":" + param.Type + "]"
643+
}
591644
}
592645
if paramStr != "" {
593646
fmt.Fprintf(os.Stderr, " Parameters: %s\n", paramStr)

0 commit comments

Comments
 (0)