execute_code: add tool/args params, read from 9P, trust tools
- Add 'tool' param to read tool code from 9P namespace - Add 'args' as array (passed as positional params via set --) - Tools from 9P skip dangerous pattern validation (trusted) - Add Go language support - Update all skills to use tool/args syntax
This commit is contained in:
parent
9643a9a034
commit
b3bb801b23
|
|
@ -0,0 +1 @@
|
|||
{"issue_prefix":"TST"}
|
||||
|
|
@ -157,24 +157,26 @@ func loadLayeredConfig(name string) (*sandbox.Config, error) {
|
|||
return sandbox.Merge(general, advanced, layers...), nil
|
||||
}
|
||||
|
||||
func executeCode(code, language string, timeout int, sandboxName string) (string, error) {
|
||||
func executeCode(code, language string, timeout int, sandboxName string, trusted bool) (string, error) {
|
||||
start := time.Now()
|
||||
|
||||
if timeout <= 0 {
|
||||
timeout = 30
|
||||
}
|
||||
|
||||
if err := validateCode(code); err != nil {
|
||||
logExecution(ExecutionLog{
|
||||
Timestamp: start,
|
||||
CodeHash: hashCode(code),
|
||||
Language: language,
|
||||
Duration: time.Since(start),
|
||||
Success: false,
|
||||
OutputSize: 0,
|
||||
Error: err.Error(),
|
||||
})
|
||||
return "", err
|
||||
if !trusted {
|
||||
if err := validateCode(code); err != nil {
|
||||
logExecution(ExecutionLog{
|
||||
Timestamp: start,
|
||||
CodeHash: hashCode(code),
|
||||
Language: language,
|
||||
Duration: time.Since(start),
|
||||
Success: false,
|
||||
OutputSize: 0,
|
||||
Error: err.Error(),
|
||||
})
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Load layered sandbox config
|
||||
|
|
@ -256,8 +258,43 @@ func executeCode(code, language string, timeout int, sandboxName string) (string
|
|||
|
||||
cmd = exec.CommandContext(ctx, landrunPath, args...)
|
||||
cmd.Dir = workDir
|
||||
case "go":
|
||||
args := []string{}
|
||||
|
||||
addPaths := func(flag string, paths []string) {
|
||||
for _, p := range paths {
|
||||
expanded := sandbox.ExpandPath(p, workDir)
|
||||
if expanded == "" || strings.Contains(expanded, "{") {
|
||||
continue
|
||||
}
|
||||
if _, err := os.Stat(expanded); err == nil {
|
||||
args = append(args, flag, expanded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addPaths("--ro", cfg.Filesystem.RO)
|
||||
addPaths("--rox", cfg.Filesystem.ROX)
|
||||
addPaths("--rw", cfg.Filesystem.RW)
|
||||
addPaths("--rwx", cfg.Filesystem.RWX)
|
||||
|
||||
if cfg.Network.Unrestricted {
|
||||
args = append(args, "--unrestricted-network")
|
||||
}
|
||||
|
||||
for _, env := range cfg.Env {
|
||||
if os.Getenv(env) != "" || env == "HOME" || env == "USER" || env == "PATH" {
|
||||
args = append(args, "--env", env)
|
||||
}
|
||||
}
|
||||
|
||||
args = append(args, "--", "go", "run", "-")
|
||||
|
||||
cmd = exec.CommandContext(ctx, landrunPath, args...)
|
||||
cmd.Dir = workDir
|
||||
cmd.Stdin = strings.NewReader(code)
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported language: %s (only bash supported)", language)
|
||||
return "", fmt.Errorf("unsupported language: %s (supported: bash, go)", language)
|
||||
}
|
||||
|
||||
// Limit output size (10MB)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"9fans.net/go/plan9/client"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -54,6 +58,40 @@ type Items struct {
|
|||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// readTool reads a tool from the 9P tools directory
|
||||
func readTool(name string) (string, error) {
|
||||
// Prevent path traversal
|
||||
if strings.Contains(name, "/") || strings.Contains(name, "..") {
|
||||
return "", fmt.Errorf("invalid tool name")
|
||||
}
|
||||
|
||||
ns := fmt.Sprintf("/tmp/ns.%s.:0", os.Getenv("USER"))
|
||||
fsys, err := client.Mount("unix", filepath.Join(ns, "agent"))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to mount 9P: %v", err)
|
||||
}
|
||||
defer fsys.Close()
|
||||
|
||||
fid, err := fsys.Open("/tools/"+name, 0)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("tool not found: %s", name)
|
||||
}
|
||||
defer fid.Close()
|
||||
|
||||
var buf []byte
|
||||
tmp := make([]byte, 8192)
|
||||
for {
|
||||
n, err := fid.Read(tmp)
|
||||
if n > 0 {
|
||||
buf = append(buf, tmp[:n]...)
|
||||
}
|
||||
if err != nil || n < len(tmp) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Fprintln(os.Stderr, "[anvilmcp] Starting MCP server")
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
|
|
@ -88,16 +126,17 @@ func main() {
|
|||
"tools": []Tool{
|
||||
{
|
||||
Name: "execute_code",
|
||||
Description: "Execute bash code as a subprocess with timeout",
|
||||
Description: "Execute code from tool library or inline",
|
||||
InputSchema: InputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]Property{
|
||||
"code": {Type: "string", Description: "Bash code to execute"},
|
||||
"language": {Type: "string", Description: "Programming language (bash)", Enum: []string{"bash"}},
|
||||
"tool": {Type: "string", Description: "Tool name from agent/tools/ (e.g. 'list_sessions.sh')"},
|
||||
"args": {Type: "array", Description: "Arguments to pass to tool", Items: &Items{Type: "string"}},
|
||||
"code": {Type: "string", Description: "Inline code to execute (if tool not specified)"},
|
||||
"language": {Type: "string", Description: "Programming language (auto-detected from tool extension)", Enum: []string{"bash", "go"}},
|
||||
"timeout": {Type: "integer", Description: "Timeout in seconds (default: 30)"},
|
||||
"sandbox": {Type: "string", Description: "Sandbox config name (default: default)"},
|
||||
},
|
||||
Required: []string{"code"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -124,8 +163,51 @@ func handleToolCall(req MCPRequest) {
|
|||
fmt.Fprintf(os.Stderr, "[anvilmcp] Tool call: %s with args: %v\n", params.Name, params.Arguments)
|
||||
switch params.Name {
|
||||
case "execute_code":
|
||||
tool, _ := params.Arguments["tool"].(string)
|
||||
var toolArgs []string
|
||||
if argsRaw, ok := params.Arguments["args"].([]interface{}); ok {
|
||||
for _, a := range argsRaw {
|
||||
if s, ok := a.(string); ok {
|
||||
toolArgs = append(toolArgs, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
code, _ := params.Arguments["code"].(string)
|
||||
language, _ := params.Arguments["language"].(string)
|
||||
|
||||
// If tool specified, read from tools library
|
||||
if tool != "" {
|
||||
toolCode, err := readTool(tool)
|
||||
if err != nil {
|
||||
sendError(req.ID, -32000, fmt.Sprintf("failed to read tool %s: %v", tool, err))
|
||||
return
|
||||
}
|
||||
code = toolCode
|
||||
// Auto-detect language from extension
|
||||
if language == "" {
|
||||
switch {
|
||||
case strings.HasSuffix(tool, ".go"):
|
||||
language = "go"
|
||||
default:
|
||||
language = "bash"
|
||||
}
|
||||
}
|
||||
// For bash with args, wrap script to receive positional params
|
||||
if language == "bash" && len(toolArgs) > 0 {
|
||||
// Escape args for bash array
|
||||
var escaped []string
|
||||
for _, arg := range toolArgs {
|
||||
escaped = append(escaped, fmt.Sprintf("%q", arg))
|
||||
}
|
||||
code = fmt.Sprintf("set -- %s\n%s", strings.Join(escaped, " "), code)
|
||||
}
|
||||
}
|
||||
|
||||
if code == "" {
|
||||
sendError(req.ID, -32602, "either 'tool' or 'code' is required")
|
||||
return
|
||||
}
|
||||
|
||||
if language == "" {
|
||||
language = "bash"
|
||||
}
|
||||
|
|
@ -143,13 +225,14 @@ func handleToolCall(req MCPRequest) {
|
|||
executionSemaphore <- struct{}{}
|
||||
defer func() { <-executionSemaphore }()
|
||||
|
||||
fmt.Fprintf(os.Stderr, "[anvilmcp] Executing bash code (timeout: %ds, sandbox: %s)\n", timeout, sandbox)
|
||||
result, err := executeCode(code, language, timeout, sandbox)
|
||||
trusted := tool != "" // Tools from 9P are trusted
|
||||
fmt.Fprintf(os.Stderr, "[anvilmcp] Executing %s code (timeout: %ds, sandbox: %s, trusted: %v)\n", language, timeout, sandbox, trusted)
|
||||
result, err := executeCode(code, language, timeout, sandbox, trusted)
|
||||
|
||||
// Fallback to default sandbox on permission errors (only if sandbox wasn't explicit)
|
||||
if err != nil && !sandboxExplicit && isPermissionError(err) {
|
||||
fmt.Fprintf(os.Stderr, "[anvilmcp] Permission error in %s sandbox, falling back to default\n", sandbox)
|
||||
result, err = executeCode(code, language, timeout, "default")
|
||||
result, err = executeCode(code, language, timeout, "default", trusted)
|
||||
}
|
||||
|
||||
// Log token comparison
|
||||
|
|
|
|||
|
|
@ -14,22 +14,21 @@ when_to_load: Load when you need to send messages to other agents, respond to th
|
|||
Discover agents:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/list_sessions.sh) | grep "$(pwd)"
|
||||
```
|
||||
|
||||
Send message:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/send_message.sh) <FROM> <TO> <TYPE> <SUBJECT> <BODY>
|
||||
tool: send_message.sh
|
||||
args: ["<from>", "<to>", "<type>", "<subject>", "<body>"]
|
||||
```
|
||||
|
||||
Check inbox:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/read_inbox.sh) <AGENT_ID>
|
||||
tool: read_inbox.sh
|
||||
args: ["<agent_id>"]
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
|
@ -52,5 +51,3 @@ code: bash <(9p read agent/tools/read_inbox.sh) <AGENT_ID>
|
|||
1. Check your inbox
|
||||
2. Follow the instructions in the message
|
||||
3. Respond to the sender appropriately (see Sending Messages)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -10,41 +10,39 @@ Create and control agent sessions.
|
|||
|
||||
## Commands
|
||||
|
||||
All commands use `execute_code` with `sandbox: "anvilmcp"`, EXCEPT spawn_agent.sh which requires `sandbox: "default"`.
|
||||
|
||||
List sessions:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/list_sessions.sh)
|
||||
tool: list_sessions.sh
|
||||
```
|
||||
|
||||
Create session:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/create_session.sh) <backend> <cwd> [sandbox=<sandbox>] [model=<model>]
|
||||
tool: create_session.sh
|
||||
args: ["<backend>", "<cwd>", "sandbox=<sandbox>", "model=<model>"]
|
||||
```
|
||||
|
||||
Control session:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/control_session.sh) <session-id> <command>
|
||||
tool: control_session.sh
|
||||
args: ["<session-id>", "<command>"]
|
||||
```
|
||||
|
||||
Spawn agent:
|
||||
Spawn agent (requires default sandbox):
|
||||
```
|
||||
Tool: execute_code
|
||||
tool: spawn_agent.sh
|
||||
args: ["<agent-id>", "<cwd-path>", "<initial-context-prompt>"]
|
||||
sandbox: default
|
||||
code: bash <(9p read agent/tools/spawn_agent.sh) <agent-id> [cwd-path] [initial-context-prompt]
|
||||
```
|
||||
|
||||
Kill agent:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/kill_agent.sh) [cwd-path]
|
||||
tool: kill_agent.sh
|
||||
args: ["<cwd-path>"]
|
||||
```
|
||||
|
||||
## Backends
|
||||
|
|
|
|||
|
|
@ -11,15 +11,21 @@ description: Manage tasks using the beads 9P interface. Use when creating, updat
|
|||
Find your project's beads mount:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: MOUNT=$(bash <(9p read agent/tools/list_mounts.sh) | grep "$(pwd)" | awk '{print $1}'); echo $MOUNT
|
||||
```
|
||||
|
||||
If no mount exists, create one:
|
||||
If no mount exists, create one (substitute actual path):
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/mount_beads.sh) "$(pwd)"
|
||||
tool: mount_beads.sh
|
||||
args: ["/path/to/project"]
|
||||
```
|
||||
|
||||
Initialize beads in a mount (creates .beads/):
|
||||
```
|
||||
Tool: execute_code
|
||||
tool: init_beads.sh
|
||||
args: ["<mount>", "<prefix>"]
|
||||
```
|
||||
|
||||
## Tools
|
||||
|
|
@ -29,36 +35,35 @@ All tools require `<mount>` as first argument. Exceptions: `list_mounts.sh`, `mo
|
|||
List mounts:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/list_mounts.sh)
|
||||
tool: list_mounts.sh
|
||||
```
|
||||
|
||||
List ready beads:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/list_ready_beads.sh) <mount>
|
||||
tool: list_ready_beads.sh
|
||||
args: ["<mount>"]
|
||||
```
|
||||
|
||||
List all beads:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/list_beads.sh) <mount>
|
||||
tool: list_beads.sh
|
||||
args: ["<mount>"]
|
||||
```
|
||||
|
||||
Read bead JSON:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/read_bead.sh) <mount> <id> json
|
||||
tool: read_bead.sh
|
||||
args: ["<mount>", "<id>", "json"]
|
||||
```
|
||||
|
||||
Read bead comments:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/read_bead.sh) <mount> <id> comments
|
||||
tool: read_bead.sh
|
||||
args: ["<mount>", "<id>", "comments"]
|
||||
```
|
||||
|
||||
**IMPORTANT:** When working on a bead, always check `comment_count` in the JSON output. If > 0, read comments for additional context, decisions, or blockers.
|
||||
|
|
@ -66,64 +71,64 @@ code: bash <(9p read agent/tools/read_bead.sh) <mount> <id> comments
|
|||
Claim bead:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/claim_bead.sh) <mount> <id> [$AGENT_ID]
|
||||
tool: claim_bead.sh
|
||||
args: ["<mount>", "<id>", "$AGENT_ID"]
|
||||
```
|
||||
|
||||
Complete bead:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/complete_bead.sh) <mount> <id>
|
||||
tool: complete_bead.sh
|
||||
args: ["<mount>", "<id>"]
|
||||
```
|
||||
|
||||
Fail bead:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/fail_bead.sh) <mount> <id> "reason"
|
||||
tool: fail_bead.sh
|
||||
args: ["<mount>", "<id>", "<reason>"]
|
||||
```
|
||||
|
||||
Create bead:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/create_bead.sh) <mount> "title" "desc" [parent]
|
||||
tool: create_bead.sh
|
||||
args: ["<mount>", "<title>", "<desc>", "<parent>"]
|
||||
```
|
||||
|
||||
Update bead:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/update_bead.sh) <mount> <id> <field> <value>
|
||||
tool: update_bead.sh
|
||||
args: ["<mount>", "<id>", "<field>", "<value>"]
|
||||
```
|
||||
|
||||
Add dependency:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/add_dependency.sh) <mount> <child> <parent>
|
||||
tool: add_dependency.sh
|
||||
args: ["<mount>", "<child>", "<parent>"]
|
||||
```
|
||||
|
||||
Comment on bead:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/comment_bead.sh) <mount> <id> "text"
|
||||
tool: comment_bead.sh
|
||||
args: ["<mount>", "<id>", "<text>"]
|
||||
```
|
||||
|
||||
Label bead:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/label_bead.sh) <mount> <id> <label>
|
||||
tool: label_bead.sh
|
||||
args: ["<mount>", "<id>", "<label>"]
|
||||
```
|
||||
|
||||
Delete bead:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/delete_bead.sh) <mount> <id>
|
||||
tool: delete_bead.sh
|
||||
args: ["<mount>", "<id>"]
|
||||
```
|
||||
|
||||
**Note:** Delete does not cascade. To delete a parent and all children, delete children first, then parent.
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ Import GitHub issues into beads.
|
|||
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/github_to_beads.sh) OWNER/REPO ISSUE_NUMBER
|
||||
tool: github_to_beads.sh
|
||||
args: ["<owner/repo>", "<issue_number>"]
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ Import Jira ticket hierarchies into beads.
|
|||
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/jira_to_beads.sh) PROJ-12345
|
||||
tool: jira_to_beads.sh
|
||||
args: ["<ticket-key>"]
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
|
|
|||
|
|
@ -40,29 +40,30 @@ Load pcloudcc-testing skill. Test the affected code path with unit tests, fault
|
|||
Spawn testing agent:
|
||||
```
|
||||
Tool: execute_code
|
||||
tool: spawn_agent.sh
|
||||
args: ["<your-id>", "<cwd>", "<system-prompt-above>"]
|
||||
sandbox: default
|
||||
code: bash <(9p read agent/tools/spawn_agent.sh) <your-id> "<system-prompt-above>"
|
||||
```
|
||||
|
||||
Send test request:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/send_message.sh) <your-id> <testing-agent-id> PROMPT_REQUEST "Validate fix" "Bug: <desc>\nFix: <changes>\nFiles: <list>\nBranch: <name>"
|
||||
tool: send_message.sh
|
||||
args: ["<your-id>", "<testing-agent-id>", "PROMPT_REQUEST", "Validate fix", "Bug: <desc>\nFix: <changes>\nFiles: <list>\nBranch: <name>"]
|
||||
```
|
||||
|
||||
Read response (user notifies when ready):
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/read_inbox.sh) <your-id>
|
||||
tool: read_inbox.sh
|
||||
args: ["<your-id>"]
|
||||
```
|
||||
|
||||
Kill testing agent:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/kill_agent.sh) <testing-agent-id>
|
||||
tool: kill_agent.sh
|
||||
args: ["<testing-agent-id>"]
|
||||
```
|
||||
|
||||
### Testing Agent Workflow
|
||||
|
|
@ -75,8 +76,8 @@ code: bash <(9p read agent/tools/kill_agent.sh) <testing-agent-id>
|
|||
6. Report:
|
||||
```
|
||||
Tool: execute_code
|
||||
sandbox: anvilmcp
|
||||
code: bash <(9p read agent/tools/send_message.sh) <your-id> <requester-id> PROMPT_RESPONSE "Validation complete" "Status: completed|failed\n<evidence>"
|
||||
tool: send_message.sh
|
||||
args: ["<your-id>", "<requester-id>", "PROMPT_RESPONSE", "Validation complete", "Status: completed|failed\n<evidence>"]
|
||||
```
|
||||
|
||||
**FUSE testing**: Inform user daemon must start outside sandbox, wait for acknowledgement
|
||||
|
|
|
|||
Reference in New Issue