Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"encoding/json"
"fmt"
"net"
"os"
"regexp"
"strconv"
Expand All @@ -13,6 +14,7 @@ import (
)

const agentConfigPath = "/agyn-bin/config.json"
const defaultMCPHost = "127.0.0.1"

var mcpServerNamePattern = regexp.MustCompile(`^[a-z][a-z0-9_]*$`)

Expand All @@ -36,6 +38,7 @@ type Config struct {
SDK string
AgentBinary string
WorkDir string
MCPHost string
MCPServers []MCPServer
}

Expand Down Expand Up @@ -66,6 +69,18 @@ func fromEnv(configPath string) (Config, error) {
llmAPIToken = "platform"
}

mcpHost := strings.TrimSpace(os.Getenv("AGENT_MCP_HOST"))
if mcpHost == "" {
mcpHost = defaultMCPHost
} else {
if strings.Contains(mcpHost, "://") || strings.Contains(mcpHost, "/") {
return Config{}, fmt.Errorf("AGENT_MCP_HOST must be a hostname without scheme or path")
}
if host, port, err := net.SplitHostPort(mcpHost); err == nil && host != "" && port != "" {
return Config{}, fmt.Errorf("AGENT_MCP_HOST must not include a port")
}
}

mcpServers, err := parseMCPServers(os.Getenv("AGENT_MCP_SERVERS"))
if err != nil {
return Config{}, err
Expand Down Expand Up @@ -99,6 +114,7 @@ func fromEnv(configPath string) (Config, error) {
SDK: sdk,
AgentBinary: agentBinary,
WorkDir: workDir,
MCPHost: mcpHost,
MCPServers: mcpServers,
}, nil
}
Expand Down
35 changes: 35 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func TestFromEnvValid(t *testing.T) {
t.Setenv("THREAD_ID", "thread-123")
t.Setenv("LLM_BASE_URL", "https://llm.example")
t.Setenv("LLM_API_TOKEN", "token-123")
t.Setenv("AGENT_MCP_HOST", "mcp.internal")

cfg, err := fromEnv(configPath)
if err != nil {
Expand Down Expand Up @@ -72,6 +73,9 @@ func TestFromEnvValid(t *testing.T) {
if cfg.WorkDir != "/tmp/workdir" {
t.Fatalf("unexpected work dir: %s", cfg.WorkDir)
}
if cfg.MCPHost != "mcp.internal" {
t.Fatalf("unexpected MCP host: %s", cfg.MCPHost)
}
}

func TestFromEnvLLMAPITokenDefault(t *testing.T) {
Expand Down Expand Up @@ -126,6 +130,37 @@ func TestFromEnvDefaults(t *testing.T) {
}
}

func TestFromEnvMCPHostDefault(t *testing.T) {
setRequiredEnv(t)
configPath := writeAgentConfig(t, "codex", "/opt/bin/codex")
t.Setenv("AGENT_MCP_HOST", "")

cfg, err := fromEnv(configPath)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if cfg.MCPHost != "127.0.0.1" {
t.Fatalf("expected default MCP host, got %s", cfg.MCPHost)
}
}

func TestFromEnvMCPHostInvalid(t *testing.T) {
setRequiredEnv(t)
configPath := writeAgentConfig(t, "codex", "/opt/bin/codex")

t.Setenv("AGENT_MCP_HOST", "http://localhost")
_, err := fromEnv(configPath)
if err == nil {
t.Fatalf("expected error for AGENT_MCP_HOST with scheme")
}

t.Setenv("AGENT_MCP_HOST", "localhost:8100")
_, err = fromEnv(configPath)
if err == nil {
t.Fatalf("expected error for AGENT_MCP_HOST with port")
}
}

func TestFromEnvGatewayDefault(t *testing.T) {
setRequiredEnv(t)
configPath := writeAgentConfig(t, "codex", "/opt/bin/codex")
Expand Down
3 changes: 2 additions & 1 deletion internal/daemon/agn.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ func newAgnDaemon(ctx context.Context, cfg config.Config, version string) (*Daem
cfg.LLMAPIToken,
setup.agent.GetModel(),
summarization,
cfg.MCPHost,
cfg.MCPServers,
)
if err != nil {
_ = setup.gatewayConn.Close()
return nil, err
}

if err := waitForMCPServers(ctx, cfg.MCPServers, mcpReadyTimeout); err != nil {
if err := waitForMCPServers(ctx, cfg.MCPServers, cfg.MCPHost, mcpReadyTimeout); err != nil {
_ = setup.gatewayConn.Close()
return nil, err
}
Expand Down
8 changes: 4 additions & 4 deletions internal/daemon/agnconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const agnConfigTemplate = `llm:
model: %s
`

func writeAgnConfig(llmBaseURL, apiKey, model string, summarization *summarizationConfig, mcpServers []config.MCPServer) (string, string, error) {
func writeAgnConfig(llmBaseURL, apiKey, model string, summarization *summarizationConfig, mcpHost string, mcpServers []config.MCPServer) (string, string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", "", fmt.Errorf("resolve home directory: %w", err)
Expand All @@ -26,14 +26,14 @@ func writeAgnConfig(llmBaseURL, apiKey, model string, summarization *summarizati
return "", "", fmt.Errorf("create agn config dir: %w", err)
}
configPath := filepath.Join(agnDir, "config.yaml")
payload := agnConfig(llmBaseURL, apiKey, model, summarization, mcpServers)
payload := agnConfig(llmBaseURL, apiKey, model, summarization, mcpHost, mcpServers)
if err := os.WriteFile(configPath, []byte(payload), 0o600); err != nil {
return "", "", fmt.Errorf("write agn config: %w", err)
}
return agnDir, configPath, nil
}

func agnConfig(llmBaseURL, apiKey, model string, summarization *summarizationConfig, mcpServers []config.MCPServer) string {
func agnConfig(llmBaseURL, apiKey, model string, summarization *summarizationConfig, mcpHost string, mcpServers []config.MCPServer) string {
payload := fmt.Sprintf(agnConfigTemplate, llmBaseURL, apiKey, model)
if summarization == nil && len(mcpServers) == 0 {
return payload
Expand All @@ -46,7 +46,7 @@ func agnConfig(llmBaseURL, apiKey, model string, summarization *summarizationCon
if len(mcpServers) > 0 {
builder.WriteString("mcp:\n servers:\n")
for _, server := range mcpServers {
url := fmt.Sprintf("http://localhost:%d/mcp", server.Port)
url := mcpServerURL(mcpHost, server.Port)
fmt.Fprintf(&builder, " %s:\n url: %s\n", server.Name, url)
}
}
Expand Down
16 changes: 8 additions & 8 deletions internal/daemon/agnconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestWriteAgnConfig(t *testing.T) {
baseURL := "https://example.com"
apiKey := "test-api-key"
model := "test-model-id"
agnDir, configPath, err := writeAgnConfig(baseURL, apiKey, model, nil, nil)
agnDir, configPath, err := writeAgnConfig(baseURL, apiKey, model, nil, "127.0.0.1", nil)
if err != nil {
t.Fatalf("expected config to be written, got %v", err)
}
Expand Down Expand Up @@ -56,7 +56,7 @@ func TestAgnConfigNoSummarizationInJSON(t *testing.T) {
t.Fatalf("expected nil summarization, got %#v", summarization)
}

content := agnConfig(baseURL, apiKey, model, summarization, nil)
content := agnConfig(baseURL, apiKey, model, summarization, "127.0.0.1", nil)
expected := fmt.Sprintf(agnConfigTemplate, baseURL, apiKey, model)
if content != expected {
t.Fatalf("expected config %q, got %q", expected, content)
Expand All @@ -74,7 +74,7 @@ func TestWriteAgnConfigWithMCPServers(t *testing.T) {
{Name: "memory", Port: 8100},
{Name: "filesystem", Port: 8200},
}
agnDir, configPath, err := writeAgnConfig(baseURL, apiKey, model, nil, mcpServers)
agnDir, configPath, err := writeAgnConfig(baseURL, apiKey, model, nil, "127.0.0.1", mcpServers)
if err != nil {
t.Fatalf("expected config to be written, got %v", err)
}
Expand All @@ -96,8 +96,8 @@ func TestWriteAgnConfigWithMCPServers(t *testing.T) {

expected := fmt.Sprintf(agnConfigTemplate, baseURL, apiKey, model) +
"mcp:\n servers:\n" +
" memory:\n url: http://localhost:8100/mcp\n" +
" filesystem:\n url: http://localhost:8200/mcp\n"
" memory:\n url: http://127.0.0.1:8100/mcp\n" +
" filesystem:\n url: http://127.0.0.1:8200/mcp\n"
if string(content) != expected {
t.Fatalf("expected config %q, got %q", expected, string(content))
}
Expand All @@ -116,7 +116,7 @@ func TestWriteAgnConfigWithSummarizationThresholds(t *testing.T) {
KeepTokens: &keepTokens,
MaxTokens: &maxTokens,
}
_, configPath, err := writeAgnConfig(baseURL, apiKey, model, summarization, nil)
_, configPath, err := writeAgnConfig(baseURL, apiKey, model, summarization, "127.0.0.1", nil)
if err != nil {
t.Fatalf("expected config to be written, got %v", err)
}
Expand Down Expand Up @@ -155,7 +155,7 @@ func TestWriteAgnConfigWithSummarizationLLMAPIKey(t *testing.T) {
Model: "gpt-4.1-mini",
},
}
agnDir, configPath, err := writeAgnConfig(baseURL, apiKey, model, summarization, nil)
agnDir, configPath, err := writeAgnConfig(baseURL, apiKey, model, summarization, "127.0.0.1", nil)
if err != nil {
t.Fatalf("expected config to be written, got %v", err)
}
Expand Down Expand Up @@ -209,7 +209,7 @@ func TestWriteAgnConfigWithSummarizationLLMAPIKeyEnv(t *testing.T) {
Model: "gpt-4.1-mini",
},
}
_, configPath, err := writeAgnConfig(baseURL, apiKey, model, summarization, nil)
_, configPath, err := writeAgnConfig(baseURL, apiKey, model, summarization, "127.0.0.1", nil)
if err != nil {
t.Fatalf("expected config to be written, got %v", err)
}
Expand Down
5 changes: 3 additions & 2 deletions internal/daemon/claude.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ func newClaudeDaemon(ctx context.Context, cfg config.Config, version string) (*D
return nil, err
}

if err := writeClaudeSettings(claudeBaseURL(cfg.LLMBaseURL), cfg.LLMAPIToken, cfg.MCPServers); err != nil {
if err := writeClaudeSettings(claudeBaseURL(cfg.LLMBaseURL), cfg.LLMAPIToken, cfg.MCPHost, cfg.MCPServers); err != nil {
_ = setup.gatewayConn.Close()
return nil, err
}

if err := waitForMCPServers(ctx, cfg.MCPServers, mcpReadyTimeout); err != nil {
if err := waitForMCPServers(ctx, cfg.MCPServers, cfg.MCPHost, mcpReadyTimeout); err != nil {
_ = setup.gatewayConn.Close()
return nil, err
}
Expand All @@ -47,6 +47,7 @@ func newClaudeDaemon(ctx context.Context, cfg config.Config, version string) (*D
Env: []string{
"LD_LIBRARY_PATH=/agyn-bin/lib",
"OTEL_EXPORTER_OTLP_ENDPOINT=" + otlpEndpoint,
"IS_SANDBOX=1",
},
}
if model := strings.TrimSpace(setup.agent.GetModel()); model != "" {
Expand Down
4 changes: 2 additions & 2 deletions internal/daemon/claudeconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type claudeMCPServer struct {
URL string `json:"url"`
}

func writeClaudeSettings(llmBaseURL, apiKey string, mcpServers []config.MCPServer) error {
func writeClaudeSettings(llmBaseURL, apiKey, mcpHost string, mcpServers []config.MCPServer) error {
home, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("resolve home directory: %w", err)
Expand Down Expand Up @@ -68,7 +68,7 @@ func writeClaudeSettings(llmBaseURL, apiKey string, mcpServers []config.MCPServe
for _, server := range mcpServers {
settings.MCPServers[server.Name] = claudeMCPServer{
Type: "http",
URL: fmt.Sprintf("http://localhost:%d/mcp", server.Port),
URL: mcpServerURL(mcpHost, server.Port),
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions internal/daemon/claudeconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestWriteClaudeSettings(t *testing.T) {

baseURL := "https://example.com"
apiKey := "test-api-key"
if err := writeClaudeSettings(baseURL, apiKey, nil); err != nil {
if err := writeClaudeSettings(baseURL, apiKey, "127.0.0.1", nil); err != nil {
t.Fatalf("expected settings to be written, got %v", err)
}

Expand Down Expand Up @@ -73,7 +73,7 @@ func TestWriteClaudeSettingsWithMCPServers(t *testing.T) {
{Name: "memory", Port: 8100},
{Name: "cache", Port: 8200},
}
if err := writeClaudeSettings(baseURL, apiKey, mcpServers); err != nil {
if err := writeClaudeSettings(baseURL, apiKey, "127.0.0.1", mcpServers); err != nil {
t.Fatalf("expected settings to be written, got %v", err)
}

Expand Down Expand Up @@ -115,8 +115,8 @@ func TestWriteClaudeSettingsWithMCPServers(t *testing.T) {
"DISABLE_AUTOUPDATER": "1",
},
MCPServers: map[string]claudeMCPServer{
"memory": {Type: "http", URL: "http://localhost:8100/mcp"},
"cache": {Type: "http", URL: "http://localhost:8200/mcp"},
"memory": {Type: "http", URL: "http://127.0.0.1:8100/mcp"},
"cache": {Type: "http", URL: "http://127.0.0.1:8200/mcp"},
},
}
if !reflect.DeepEqual(got, expected) {
Expand Down
12 changes: 7 additions & 5 deletions internal/daemon/codexconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strings"
"time"

"github.com/agynio/agynd-cli/internal/config"
)
Expand All @@ -20,7 +21,7 @@ env_key = "OPENAI_API_KEY"
wire_api = "responses"
`

func writeCodexConfig(llmBaseURL string, mcpServers []config.MCPServer) (string, error) {
func writeCodexConfig(llmBaseURL, mcpHost string, mcpServers []config.MCPServer) (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("resolve home directory: %w", err)
Expand All @@ -30,23 +31,24 @@ func writeCodexConfig(llmBaseURL string, mcpServers []config.MCPServer) (string,
return "", fmt.Errorf("create codex home dir: %w", err)
}
configPath := filepath.Join(codexHome, "config.toml")
payload := codexConfig(llmBaseURL, mcpServers)
payload := codexConfig(llmBaseURL, mcpHost, mcpServers)
if err := os.WriteFile(configPath, []byte(payload), 0o600); err != nil {
return "", fmt.Errorf("write codex config: %w", err)
}
return codexHome, nil
}

func codexConfig(llmBaseURL string, mcpServers []config.MCPServer) string {
func codexConfig(llmBaseURL, mcpHost string, mcpServers []config.MCPServer) string {
payload := fmt.Sprintf(codexConfigTemplate, llmBaseURL)
if len(mcpServers) == 0 {
return payload
}
var builder strings.Builder
builder.WriteString(payload)
for _, server := range mcpServers {
url := fmt.Sprintf("http://localhost:%d/mcp", server.Port)
fmt.Fprintf(&builder, "\n[mcp_servers.%s]\nurl = %q\nrequired = true\nstartup_timeout_sec = 120\n", server.Name, url)
url := mcpServerURL(mcpHost, server.Port)
startupTimeout := int(mcpReadyTimeout / time.Second)
fmt.Fprintf(&builder, "\n[mcp_servers.%s]\nurl = %q\nrequired = true\nstartup_timeout_sec = %d\n", server.Name, url, startupTimeout)
}
return builder.String()
}
12 changes: 6 additions & 6 deletions internal/daemon/codexconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func TestWriteCodexConfig(t *testing.T) {
t.Setenv("HOME", tmpHome)

baseURL := "https://example.com"
codexHome, err := writeCodexConfig(baseURL, nil)
codexHome, err := writeCodexConfig(baseURL, "127.0.0.1", nil)
if err != nil {
t.Fatalf("expected config to be written, got %v", err)
}
Expand Down Expand Up @@ -54,7 +54,7 @@ func TestWriteCodexConfigWithMCPServers(t *testing.T) {
{Name: "memory", Port: 8100},
{Name: "cache", Port: 8200},
}
codexHome, err := writeCodexConfig(baseURL, mcpServers)
codexHome, err := writeCodexConfig(baseURL, "127.0.0.1", mcpServers)
if err != nil {
t.Fatalf("expected config to be written, got %v", err)
}
Expand All @@ -81,13 +81,13 @@ env_key = "OPENAI_API_KEY"
wire_api = "responses"
`, baseURL) +
"\n[mcp_servers.memory]\n" +
"url = \"http://localhost:8100/mcp\"\n" +
"url = \"http://127.0.0.1:8100/mcp\"\n" +
"required = true\n" +
"startup_timeout_sec = 120\n" +
"startup_timeout_sec = 180\n" +
"\n[mcp_servers.cache]\n" +
"url = \"http://localhost:8200/mcp\"\n" +
"url = \"http://127.0.0.1:8200/mcp\"\n" +
"required = true\n" +
"startup_timeout_sec = 120\n"
"startup_timeout_sec = 180\n"
if string(content) != expected {
t.Fatalf("expected config %q, got %q", expected, string(content))
}
Expand Down
Loading
Loading