diff --git a/pkg/gateway/clientpool.go b/pkg/gateway/clientpool.go index bac0eda9..2f1fe52c 100644 --- a/pkg/gateway/clientpool.go +++ b/pkg/gateway/clientpool.go @@ -352,7 +352,10 @@ func (cp *clientPool) argsAndEnv(serverConfig *catalog.ServerConfig, readOnly *b continue } - if readOnly != nil && *readOnly && !strings.HasSuffix(mount, ":ro") { + // For long-lived servers, never mount volumes as read-only + // because the container will be shared across multiple tool calls + isLongLived := serverConfig.Spec.LongLived || cp.LongLived + if !isLongLived && readOnly != nil && *readOnly && !strings.HasSuffix(mount, ":ro") { args = append(args, "-v", mount+":ro") } else { args = append(args, "-v", mount) diff --git a/pkg/gateway/clientpool_test.go b/pkg/gateway/clientpool_test.go index 36a5e577..7adaf837 100644 --- a/pkg/gateway/clientpool_test.go +++ b/pkg/gateway/clientpool_test.go @@ -160,6 +160,42 @@ user: "1001:2002" assert.Empty(t, env) } +func TestApplyConfigLongLivedIgnoresReadOnly(t *testing.T) { + catalogYAML := ` +longLived: true +volumes: + - '/local/data:/data' + ` + + args, env := argsAndEnv(t, "longlived", catalogYAML, "", nil, readOnly()) + + // Even though readOnly is true, volumes should NOT have :ro appended + // because long-lived servers share containers across multiple tool calls + assert.Equal(t, []string{ + "run", "--rm", "-i", "--init", "--security-opt", "no-new-privileges", "--cpus", "1", "--memory", "2Gb", "--pull", "never", + "-l", "docker-mcp=true", "-l", "docker-mcp-tool-type=mcp", "-l", "docker-mcp-name=longlived", "-l", "docker-mcp-transport=stdio", + "-v", "/local/data:/data", + }, args) + assert.Empty(t, env) +} + +func TestApplyConfigShortLivedRespectsReadOnly(t *testing.T) { + catalogYAML := ` +volumes: + - '/local/data:/data' + ` + + args, env := argsAndEnv(t, "shortlived", catalogYAML, "", nil, readOnly()) + + // Short-lived servers should respect readOnly flag + assert.Equal(t, []string{ + "run", "--rm", "-i", "--init", "--security-opt", "no-new-privileges", "--cpus", "1", "--memory", "2Gb", "--pull", "never", + "-l", "docker-mcp=true", "-l", "docker-mcp-tool-type=mcp", "-l", "docker-mcp-name=shortlived", "-l", "docker-mcp-transport=stdio", + "-v", "/local/data:/data:ro", + }, args) + assert.Empty(t, env) +} + func argsAndEnv(t *testing.T, name, catalogYAML, configYAML string, secrets map[string]string, readOnly *bool) ([]string, []string) { t.Helper()