80 lines
1.9 KiB
Go
80 lines
1.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"os/exec"
|
|
|
|
"github.com/mark3labs/mcp-go/mcp"
|
|
)
|
|
|
|
type runArgs struct {
|
|
ID string `json:"id"`
|
|
Args []string `json:"args,omitempty"`
|
|
}
|
|
|
|
func runTool() mcp.Tool {
|
|
return mcp.NewTool("fn_run",
|
|
mcp.WithDescription("Execute a registry function/pipeline via `fn run <id> [args...]`. Dispatches by language: Go (go run/test), Python (.venv), Bash, TypeScript (tsx). Mutating side-effects possible. Off by default — server must be launched with --enable-run."),
|
|
mcp.WithString("id",
|
|
mcp.Required(),
|
|
mcp.Description("Registry ID or function name."),
|
|
),
|
|
mcp.WithArray("args",
|
|
mcp.Description("Positional args appended to fn run."),
|
|
mcp.Items(map[string]any{"type": "string"}),
|
|
),
|
|
)
|
|
}
|
|
|
|
func (d *deps) handleRun(ctx context.Context, _ mcp.CallToolRequest, args runArgs) (*mcp.CallToolResult, error) {
|
|
if args.ID == "" {
|
|
return mcp.NewToolResultError("id is required"), nil
|
|
}
|
|
bin := d.fnBin()
|
|
cmdArgs := append([]string{"run", args.ID}, args.Args...)
|
|
cmd := exec.CommandContext(ctx, bin, cmdArgs...)
|
|
cmd.Dir = d.root
|
|
cmd.Env = os.Environ()
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = &stderr
|
|
runErr := cmd.Run()
|
|
|
|
exit := 0
|
|
if cmd.ProcessState != nil {
|
|
exit = cmd.ProcessState.ExitCode()
|
|
}
|
|
|
|
out := map[string]any{
|
|
"id": args.ID,
|
|
"args": args.Args,
|
|
"exit_code": exit,
|
|
"stdout": truncate(stdout.String(), 100_000),
|
|
"stderr": truncate(stderr.String(), 100_000),
|
|
}
|
|
if runErr != nil && exit == 0 {
|
|
out["error"] = runErr.Error()
|
|
}
|
|
|
|
b, _ := json.MarshalIndent(out, "", " ")
|
|
return mcp.NewToolResultText(string(b)), nil
|
|
}
|
|
|
|
func (d *deps) fnBin() string {
|
|
if v := os.Getenv("FN_BIN"); v != "" {
|
|
return v
|
|
}
|
|
candidate := d.root + "/fn"
|
|
if _, err := os.Stat(candidate); err == nil {
|
|
return candidate
|
|
}
|
|
if path, err := exec.LookPath("fn"); err == nil {
|
|
return path
|
|
}
|
|
return "fn"
|
|
}
|