summaryrefslogtreecommitdiff
path: root/internal/executor
diff options
context:
space:
mode:
Diffstat (limited to 'internal/executor')
-rw-r--r--internal/executor/container.go42
-rw-r--r--internal/executor/container_test.go30
2 files changed, 69 insertions, 3 deletions
diff --git a/internal/executor/container.go b/internal/executor/container.go
index 78c3ed7..4f8fa06 100644
--- a/internal/executor/container.go
+++ b/internal/executor/container.go
@@ -306,16 +306,22 @@ func (r *ContainerRunner) runContainer(ctx context.Context, t *task.Task, e *sto
}
// Per-task MCP back-channel: mint a scoped token bound to this run's channel
- // and write an mcp-config pointing the agent at the host agent MCP server.
+ // and point the agent at the host agent MCP server. Claude reads an
+ // --mcp-config file; the gemini CLI auto-discovers servers from its
+ // ~/.gemini/settings.json (agentHome is the container's $HOME).
mcpEnabled := false
- if r.Registry != nil && t.Agent.Type != "gemini" {
+ if r.Registry != nil {
token, mintErr := r.Registry.Mint(ch)
if mintErr != nil {
return fmt.Errorf("minting agent mcp token: %w", mintErr)
}
defer r.Registry.Revoke(token)
mcpURL := strings.TrimRight(strings.ReplaceAll(r.APIURL, "localhost", "host.docker.internal"), "/") + "/mcp"
- if err := writeMCPConfig(workspace, mcpURL, token); err != nil {
+ if t.Agent.Type == "gemini" {
+ if err := writeGeminiMCPSettings(agentHome, mcpURL, token); err != nil {
+ return fmt.Errorf("writing gemini mcp settings: %w", err)
+ }
+ } else if err := writeMCPConfig(workspace, mcpURL, token); err != nil {
return fmt.Errorf("writing mcp config: %w", err)
}
mcpEnabled = true
@@ -534,6 +540,36 @@ func writeMCPConfig(workspace, mcpURL, token string) error {
return os.WriteFile(filepath.Join(workspace, ".claudomator-mcp.json"), data, 0600)
}
+// writeGeminiMCPSettings registers the per-task agent MCP server in the gemini
+// CLI's user settings (agentHome/.gemini/settings.json, which is $HOME/.gemini
+// in-container). The gemini CLI discovers MCP servers from this file rather than
+// a command-line flag; httpUrl selects the streamable-HTTP transport and headers
+// carry the bearer token.
+//
+// NOTE: the on-disk schema follows the gemini-cli mcpServers format, but whether
+// the CLI actually invokes these tools in non-interactive (-p) mode has not been
+// verified against a live gemini binary — confirm with a spike before relying on
+// gemini tool-use in production.
+func writeGeminiMCPSettings(agentHome, mcpURL, token string) error {
+ cfg := map[string]any{
+ "mcpServers": map[string]any{
+ "claudomator": map[string]any{
+ "httpUrl": mcpURL,
+ "headers": map[string]string{"Authorization": "Bearer " + token},
+ },
+ },
+ }
+ data, err := json.Marshal(cfg)
+ if err != nil {
+ return err
+ }
+ dir := filepath.Join(agentHome, ".gemini")
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ return err
+ }
+ return os.WriteFile(filepath.Join(dir, "settings.json"), data, 0600)
+}
+
func (r *ContainerRunner) buildInnerCmd(t *task.Task, e *storage.Execution, isResume, mcpEnabled bool) []string {
// Claude CLI uses -p for prompt text. To pass a file, we use a shell to cat it.
// We use a shell variable to capture the expansion to avoid quoting issues with instructions contents.
diff --git a/internal/executor/container_test.go b/internal/executor/container_test.go
index 3d0887c..521e1cb 100644
--- a/internal/executor/container_test.go
+++ b/internal/executor/container_test.go
@@ -107,6 +107,36 @@ func TestWriteMCPConfig(t *testing.T) {
}
}
+func TestWriteGeminiMCPSettings(t *testing.T) {
+ agentHome := t.TempDir()
+ if err := writeGeminiMCPSettings(agentHome, "http://host.docker.internal:8484/mcp", "tok-xyz"); err != nil {
+ t.Fatalf("writeGeminiMCPSettings: %v", err)
+ }
+ data, err := os.ReadFile(filepath.Join(agentHome, ".gemini", "settings.json"))
+ if err != nil {
+ t.Fatalf("read settings: %v", err)
+ }
+ var parsed struct {
+ MCPServers map[string]struct {
+ HTTPURL string `json:"httpUrl"`
+ Headers map[string]string `json:"headers"`
+ } `json:"mcpServers"`
+ }
+ if err := json.Unmarshal(data, &parsed); err != nil {
+ t.Fatalf("settings is not valid JSON: %v", err)
+ }
+ srv, ok := parsed.MCPServers["claudomator"]
+ if !ok {
+ t.Fatal("expected claudomator server entry")
+ }
+ if srv.HTTPURL != "http://host.docker.internal:8484/mcp" {
+ t.Errorf("unexpected httpUrl: %q", srv.HTTPURL)
+ }
+ if srv.Headers["Authorization"] != "Bearer tok-xyz" {
+ t.Errorf("expected bearer header, got %q", srv.Headers["Authorization"])
+ }
+}
+
func TestContainerRunner_BuildInnerCmd(t *testing.T) {
runner := &ContainerRunner{}