go-agent/cmd/mcp/core_cli.go
Snider 61e01bfdf1 feat: initial go-agent — agentci + jobrunner + plugins marketplace
Consolidates three codebases into a single agent orchestration repo:

- agentci (from go-scm): Clotho dual-run verification, agent config,
  SSH security (sanitisation, secure commands, token masking)
- jobrunner (from go-scm): Poll-dispatch-report pipeline with 7 handlers
  (dispatch, completion, auto-merge, publish draft, dismiss reviews,
  send fix command, tick parent epic)
- plugins marketplace (from agentic/plugins): 27 Claude/Codex/Gemini
  plugins with shared MCP server

All 150+ tests passing across 6 packages.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-21 15:47:19 +00:00

89 lines
2 KiB
Go

package main
import (
"bytes"
"context"
"errors"
"fmt"
"os/exec"
"strings"
"time"
"github.com/mark3labs/mcp-go/mcp"
)
var allowedCorePrefixes = map[string]struct{}{
"dev": {},
"go": {},
"php": {},
"build": {},
}
func coreCliHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
command, err := request.RequireString("command")
if err != nil {
return mcp.NewToolResultError("command is required"), nil
}
args := request.GetStringSlice("args", nil)
base, mergedArgs, err := normalizeCoreCommand(command, args)
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
execCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
result := runCoreCommand(execCtx, base, mergedArgs)
return mcp.NewToolResultStructuredOnly(result), nil
}
func normalizeCoreCommand(command string, args []string) (string, []string, error) {
parts := strings.Fields(command)
if len(parts) == 0 {
return "", nil, errors.New("command cannot be empty")
}
base := parts[0]
if _, ok := allowedCorePrefixes[base]; !ok {
return "", nil, fmt.Errorf("command not allowed: %s", base)
}
merged := append([]string{}, parts[1:]...)
merged = append(merged, args...)
return base, merged, nil
}
func runCoreCommand(ctx context.Context, command string, args []string) CoreCliResult {
cmd := exec.CommandContext(ctx, "core", append([]string{command}, args...)...)
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
exitCode := 0
if err := cmd.Run(); err != nil {
exitCode = 1
if exitErr, ok := err.(*exec.ExitError); ok {
exitCode = exitErr.ExitCode()
} else if errors.Is(err, context.DeadlineExceeded) {
exitCode = 124
}
if errors.Is(err, context.DeadlineExceeded) {
if stderr.Len() > 0 {
stderr.WriteString("\n")
}
stderr.WriteString("command timed out after 30s")
}
}
return CoreCliResult{
Command: command,
Args: args,
Stdout: stdout.String(),
Stderr: stderr.String(),
ExitCode: exitCode,
}
}