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:
Levi Neely 2026-03-06 14:07:16 +01:00
parent 9643a9a034
commit b3bb801b23
9 changed files with 206 additions and 84 deletions

1
.beads/config.json Normal file
View File

@ -0,0 +1 @@
{"issue_prefix":"TST"}

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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:**

View File

@ -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:**

View File

@ -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