agent/cmd/mcp/core_cli.go
Snider e90a84eaa0 feat: merge go-agent + go-agentic + php-devops into unified agent repo
Combines three repositories into a single workspace:
- go-agent → pkg/orchestrator (Clotho), pkg/jobrunner, pkg/loop, cmd/
- go-agentic → pkg/lifecycle (allowance, sessions, plans, dispatch)
- php-devops → repos.yaml, setup.sh, scripts/, .core/

Module path: forge.lthn.ai/core/agent

All packages build, all tests pass.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-06 15:23:00 +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,
}
}