Skip to content
Closed
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
4 changes: 3 additions & 1 deletion .github/workflows/cloclo.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion .github/workflows/developer-docs-consolidator.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion .github/workflows/duplicate-code-detector.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion .github/workflows/go-fan.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion .github/workflows/jsweep.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion .github/workflows/mcp-inspector.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion .github/workflows/smoke-claude.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion .github/workflows/smoke-codex.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion .github/workflows/typist.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pkg/workflow/claude_mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ func (e *ClaudeEngine) RenderMCPConfig(yaml *strings.Builder, tools map[string]a
renderer := createRenderer(isLast)
renderer.RenderPlaywrightMCP(yaml, playwrightTool)
},
RenderSerena: func(yaml *strings.Builder, serenaTool any, isLast bool) {
RenderSerena: func(yaml *strings.Builder, serenaTool any, isLast bool, workflowData *WorkflowData) {
renderer := createRenderer(isLast)
renderer.RenderSerenaMCP(yaml, serenaTool)
renderer.RenderSerenaMCP(yaml, serenaTool, workflowData)
},
RenderCacheMemory: e.renderCacheMemoryMCPConfig,
RenderAgenticWorkflows: func(yaml *strings.Builder, isLast bool) {
Expand Down
6 changes: 3 additions & 3 deletions pkg/workflow/codex_mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (e *CodexEngine) RenderMCPConfig(yaml *strings.Builder, tools map[string]an
renderer.RenderPlaywrightMCP(yaml, playwrightTool)
case "serena":
serenaTool := expandedTools["serena"]
renderer.RenderSerenaMCP(yaml, serenaTool)
renderer.RenderSerenaMCP(yaml, serenaTool, workflowData)
case "agentic-workflows":
renderer.RenderAgenticWorkflowsMCP(yaml)
case "safe-outputs":
Expand Down Expand Up @@ -130,9 +130,9 @@ func (e *CodexEngine) RenderMCPConfig(yaml *strings.Builder, tools map[string]an
renderer := createJSONRenderer(isLast)
renderer.RenderPlaywrightMCP(yaml, playwrightTool)
},
RenderSerena: func(yaml *strings.Builder, serenaTool any, isLast bool) {
RenderSerena: func(yaml *strings.Builder, serenaTool any, isLast bool, workflowData *WorkflowData) {
renderer := createJSONRenderer(isLast)
renderer.RenderSerenaMCP(yaml, serenaTool)
renderer.RenderSerenaMCP(yaml, serenaTool, workflowData)
},
RenderCacheMemory: func(yaml *strings.Builder, isLast bool, workflowData *WorkflowData) {
// Cache-memory is not used as MCP server
Expand Down
1 change: 1 addition & 0 deletions pkg/workflow/compiler_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ type WorkflowData struct {
SecretMasking *SecretMaskingConfig // secret masking configuration
CompilerSkipValidation *bool // compiler's skipValidation flag (passed from compiler to engines for MCP gateway schema validation)
CompilerWarningCallback func() // callback to increment compiler warning count (passed from compiler to engines for MCP gateway schema validation warnings)
ToolchainMappings *ToolchainMappings // runtime toolchain environment variables and mounts to pass to agent containers
}

// BaseSafeOutputConfig holds common configuration fields for all safe output types
Expand Down
9 changes: 9 additions & 0 deletions pkg/workflow/compiler_yaml_main_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ func (c *Compiler) generateMainJobSteps(yaml *strings.Builder, data *WorkflowDat
}
}

// Collect toolchain mappings for detected runtimes
// These mappings will be used to configure agent containers (e.g., Serena MCP server)
// with the necessary environment variables and mounts for toolchains to work
data.ToolchainMappings = CollectToolchainMappings(runtimeRequirements)
compilerYamlLog.Printf("Collected toolchain mappings: %d runtimes, %d env vars, %d mounts",
len(data.ToolchainMappings.Mappings),
len(data.ToolchainMappings.GetAllEnvVars()),
len(data.ToolchainMappings.GetAllMounts()))

// Generate runtime setup steps (after filtering out user-customized ones)
runtimeSetupSteps := GenerateRuntimeSetupSteps(runtimeRequirements)
compilerYamlLog.Printf("Detected runtime requirements: %d runtimes, %d setup steps", len(runtimeRequirements), len(runtimeSetupSteps))
Expand Down
4 changes: 2 additions & 2 deletions pkg/workflow/copilot_mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ func (e *CopilotEngine) RenderMCPConfig(yaml *strings.Builder, tools map[string]
renderer := createRenderer(isLast)
renderer.RenderPlaywrightMCP(yaml, playwrightTool)
},
RenderSerena: func(yaml *strings.Builder, serenaTool any, isLast bool) {
RenderSerena: func(yaml *strings.Builder, serenaTool any, isLast bool, workflowData *WorkflowData) {
renderer := createRenderer(isLast)
renderer.RenderSerenaMCP(yaml, serenaTool)
renderer.RenderSerenaMCP(yaml, serenaTool, workflowData)
},
RenderCacheMemory: func(yaml *strings.Builder, isLast bool, workflowData *WorkflowData) {
// Cache-memory is not used for Copilot (filtered out)
Expand Down
4 changes: 2 additions & 2 deletions pkg/workflow/custom_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,9 @@ func (e *CustomEngine) RenderMCPConfig(yaml *strings.Builder, tools map[string]a
renderer := createRenderer(isLast)
renderer.RenderPlaywrightMCP(yaml, playwrightTool)
},
RenderSerena: func(yaml *strings.Builder, serenaTool any, isLast bool) {
RenderSerena: func(yaml *strings.Builder, serenaTool any, isLast bool, workflowData *WorkflowData) {
renderer := createRenderer(isLast)
renderer.RenderSerenaMCP(yaml, serenaTool)
renderer.RenderSerenaMCP(yaml, serenaTool, workflowData)
},
RenderCacheMemory: e.renderCacheMemoryMCPConfig,
RenderAgenticWorkflows: func(yaml *strings.Builder, isLast bool) {
Expand Down
5 changes: 5 additions & 0 deletions pkg/workflow/data/action_pins.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@
"version": "v4.8.0",
"sha": "c1e323688fd81a25caa38c78aa6df2d33d3e20d9"
},
"actions/setup-node@v6": {
"repo": "actions/setup-node",
"version": "v6",
"sha": "6044e13b5dc448c55e2357c09f80417699197238"
},
"actions/setup-node@v6.1.0": {
"repo": "actions/setup-node",
"version": "v6.1.0",
Expand Down
66 changes: 63 additions & 3 deletions pkg/workflow/mcp-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func renderPlaywrightMCPConfigWithOptions(yaml *strings.Builder, playwrightTool
// renderSerenaMCPConfigWithOptions generates the Serena MCP server configuration with engine-specific options
// Per MCP Gateway Specification v1.0.0 section 3.2.1, stdio-based MCP servers MUST be containerized.
// Uses Docker container format as specified by Serena: ghcr.io/oraios/serena:latest
func renderSerenaMCPConfigWithOptions(yaml *strings.Builder, serenaTool any, isLast bool, includeCopilotFields bool, inlineArgs bool) {
func renderSerenaMCPConfigWithOptions(yaml *strings.Builder, serenaTool any, isLast bool, includeCopilotFields bool, inlineArgs bool, workflowData *WorkflowData) {
customArgs := getSerenaCustomArgs(serenaTool)

yaml.WriteString(" \"serena\": {\n")
Expand Down Expand Up @@ -193,8 +193,68 @@ func renderSerenaMCPConfigWithOptions(yaml *strings.Builder, serenaTool any, isL
yaml.WriteString(" ],\n")
}

// Add volume mount for workspace access
yaml.WriteString(" \"mounts\": [\"${{ github.workspace }}:${{ github.workspace }}:rw\"]\n")
// Collect environment variables from toolchain mappings
var envVars map[string]string
if workflowData != nil && workflowData.ToolchainMappings != nil {
envVars = workflowData.ToolchainMappings.GetAllEnvVars()
}

// Add environment variables if any
if len(envVars) > 0 {
yaml.WriteString(" \"env\": {\n")

// Sort env var names for deterministic output
var envNames []string
for name := range envVars {
envNames = append(envNames, name)
}
sort.Strings(envNames)

for i, name := range envNames {
value := envVars[name]
if i < len(envNames)-1 {
fmt.Fprintf(yaml, " \"%s\": \"%s\",\n", name, value)
} else {
fmt.Fprintf(yaml, " \"%s\": \"%s\"\n", name, value)
}
}
yaml.WriteString(" },\n")
}

// Collect mounts: base mount + toolchain mounts
baseMounts := []string{
"${{ github.workspace }}:${{ github.workspace }}:rw",
}

var allMounts []string
if workflowData != nil && workflowData.ToolchainMappings != nil {
toolchainMounts := workflowData.ToolchainMappings.GetAllMounts()
allMounts = MergeMountsWithDedup(baseMounts, toolchainMounts)
} else {
allMounts = baseMounts
}

// Add volume mounts
if inlineArgs {
yaml.WriteString(" \"mounts\": [")
for i, mount := range allMounts {
if i > 0 {
yaml.WriteString(", ")
}
fmt.Fprintf(yaml, "\"%s\"", mount)
}
yaml.WriteString("]\n")
} else {
yaml.WriteString(" \"mounts\": [\n")
for i, mount := range allMounts {
if i < len(allMounts)-1 {
fmt.Fprintf(yaml, " \"%s\",\n", mount)
} else {
fmt.Fprintf(yaml, " \"%s\"\n", mount)
}
}
yaml.WriteString(" ]\n")
}

// Note: tools field is NOT included here - the converter script adds it back
// for Copilot. This keeps the gateway config compatible with the schema.
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/mcp_config_comprehensive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ func TestRenderSerenaMCPConfigWithOptions(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
var output strings.Builder

renderSerenaMCPConfigWithOptions(&output, tt.serenaTool, tt.isLast, tt.includeCopilotFields, tt.inlineArgs)
renderSerenaMCPConfigWithOptions(&output, tt.serenaTool, tt.isLast, tt.includeCopilotFields, tt.inlineArgs, nil)

result := output.String()

Expand Down
57 changes: 49 additions & 8 deletions pkg/workflow/mcp_renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,22 +176,22 @@ func (r *MCPConfigRendererUnified) renderPlaywrightTOML(yaml *strings.Builder, p
}

// RenderSerenaMCP generates Serena MCP server configuration
func (r *MCPConfigRendererUnified) RenderSerenaMCP(yaml *strings.Builder, serenaTool any) {
func (r *MCPConfigRendererUnified) RenderSerenaMCP(yaml *strings.Builder, serenaTool any, workflowData *WorkflowData) {
mcpRendererLog.Printf("Rendering Serena MCP: format=%s, inline_args=%t", r.options.Format, r.options.InlineArgs)

if r.options.Format == "toml" {
r.renderSerenaTOML(yaml, serenaTool)
r.renderSerenaTOML(yaml, serenaTool, workflowData)
return
}

// JSON format
renderSerenaMCPConfigWithOptions(yaml, serenaTool, r.options.IsLast, r.options.IncludeCopilotFields, r.options.InlineArgs)
renderSerenaMCPConfigWithOptions(yaml, serenaTool, r.options.IsLast, r.options.IncludeCopilotFields, r.options.InlineArgs, workflowData)
}

// renderSerenaTOML generates Serena MCP configuration in TOML format
// Per MCP Gateway Specification v1.0.0 section 3.2.1, stdio-based MCP servers MUST be containerized.
// Uses Docker container format as specified by Serena: ghcr.io/oraios/serena:latest
func (r *MCPConfigRendererUnified) renderSerenaTOML(yaml *strings.Builder, serenaTool any) {
func (r *MCPConfigRendererUnified) renderSerenaTOML(yaml *strings.Builder, serenaTool any, workflowData *WorkflowData) {
customArgs := getSerenaCustomArgs(serenaTool)

yaml.WriteString(" \n")
Expand Down Expand Up @@ -221,8 +221,49 @@ func (r *MCPConfigRendererUnified) renderSerenaTOML(yaml *strings.Builder, seren
yaml.WriteString("\n")
yaml.WriteString(" ]\n")

// Add volume mount for workspace access
yaml.WriteString(" mounts = [\"${{ github.workspace }}:${{ github.workspace }}:rw\"]\n")
// Collect environment variables from toolchain mappings
var envVars map[string]string
if workflowData != nil && workflowData.ToolchainMappings != nil {
envVars = workflowData.ToolchainMappings.GetAllEnvVars()
}

// Add environment variables if any
if len(envVars) > 0 {
// Sort env var names for deterministic output
var envNames []string
for name := range envVars {
envNames = append(envNames, name)
}
sort.Strings(envNames)

for _, name := range envNames {
value := envVars[name]
fmt.Fprintf(yaml, " env.%s = \"%s\"\n", name, value)
}
}

// Collect mounts: base mount + toolchain mounts
baseMounts := []string{
"${{ github.workspace }}:${{ github.workspace }}:rw",
}

var allMounts []string
if workflowData != nil && workflowData.ToolchainMappings != nil {
toolchainMounts := workflowData.ToolchainMappings.GetAllMounts()
allMounts = MergeMountsWithDedup(baseMounts, toolchainMounts)
} else {
allMounts = baseMounts
}

// Add volume mounts
yaml.WriteString(" mounts = [")
for i, mount := range allMounts {
if i > 0 {
yaml.WriteString(", ")
}
fmt.Fprintf(yaml, "\"%s\"", mount)
}
yaml.WriteString("]\n")
}

// RenderSafeOutputsMCP generates the Safe Outputs MCP server configuration
Expand Down Expand Up @@ -471,7 +512,7 @@ func HandleCustomMCPToolInSwitch(
type MCPToolRenderers struct {
RenderGitHub func(yaml *strings.Builder, githubTool any, isLast bool, workflowData *WorkflowData)
RenderPlaywright func(yaml *strings.Builder, playwrightTool any, isLast bool)
RenderSerena func(yaml *strings.Builder, serenaTool any, isLast bool)
RenderSerena func(yaml *strings.Builder, serenaTool any, isLast bool, workflowData *WorkflowData)
RenderCacheMemory func(yaml *strings.Builder, isLast bool, workflowData *WorkflowData)
RenderAgenticWorkflows func(yaml *strings.Builder, isLast bool)
RenderSafeOutputs func(yaml *strings.Builder, isLast bool)
Expand Down Expand Up @@ -754,7 +795,7 @@ func RenderJSONMCPConfig(
options.Renderers.RenderPlaywright(&configBuilder, playwrightTool, isLast)
case "serena":
serenaTool := tools["serena"]
options.Renderers.RenderSerena(&configBuilder, serenaTool, isLast)
options.Renderers.RenderSerena(&configBuilder, serenaTool, isLast, workflowData)
case "cache-memory":
options.Renderers.RenderCacheMemory(&configBuilder, isLast, workflowData)
case "agentic-workflows":
Expand Down
Loading
Loading