Skip to content
Merged
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
93 changes: 26 additions & 67 deletions pkg/workflow/copilot_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,19 +361,40 @@ func (e *CopilotEngine) RenderMCPConfig(yaml *strings.Builder, tools map[string]
// Create the directory first
yaml.WriteString(" mkdir -p /home/runner/.copilot\n")

// Use shared JSON MCP config renderer with Copilot-specific options
// Create unified renderer with Copilot-specific options
// Copilot uses JSON format with type and tools fields, and inline args
createRenderer := func(isLast bool) *MCPConfigRendererUnified {
return NewMCPConfigRenderer(MCPRendererOptions{
IncludeCopilotFields: true, // Copilot uses "type" and "tools" fields
InlineArgs: true, // Copilot uses inline args format
Format: "json",
IsLast: isLast,
})
}

// Use shared JSON MCP config renderer with unified renderer methods
RenderJSONMCPConfig(yaml, tools, mcpTools, workflowData, JSONMCPConfigOptions{
ConfigPath: "/home/runner/.copilot/mcp-config.json",
Renderers: MCPToolRenderers{
RenderGitHub: func(yaml *strings.Builder, githubTool any, isLast bool, workflowData *WorkflowData) {
e.renderGitHubCopilotMCPConfig(yaml, githubTool, isLast)
renderer := createRenderer(isLast)
renderer.RenderGitHubMCP(yaml, githubTool, workflowData)
},
RenderPlaywright: func(yaml *strings.Builder, playwrightTool any, isLast bool) {
renderer := createRenderer(isLast)
renderer.RenderPlaywrightMCP(yaml, playwrightTool)
},
RenderPlaywright: e.renderPlaywrightCopilotMCPConfig,
RenderCacheMemory: func(yaml *strings.Builder, isLast bool, workflowData *WorkflowData) {
// Cache-memory is not used for Copilot (filtered out)
},
RenderAgenticWorkflows: e.renderAgenticWorkflowsCopilotMCPConfig,
RenderSafeOutputs: e.renderSafeOutputsCopilotMCPConfig,
RenderAgenticWorkflows: func(yaml *strings.Builder, isLast bool) {
renderer := createRenderer(isLast)
renderer.RenderAgenticWorkflowsMCP(yaml)
},
RenderSafeOutputs: func(yaml *strings.Builder, isLast bool) {
renderer := createRenderer(isLast)
renderer.RenderSafeOutputsMCP(yaml)
},
RenderWebFetch: func(yaml *strings.Builder, isLast bool) {
renderMCPFetchServerConfig(yaml, "json", " ", isLast, true)
},
Expand All @@ -398,68 +419,6 @@ func (e *CopilotEngine) RenderMCPConfig(yaml *strings.Builder, tools map[string]
yaml.WriteString(" echo \"GITHUB_COPILOT_CLI_MODE: $GITHUB_COPILOT_CLI_MODE\"\n")
}

// renderGitHubCopilotMCPConfig generates the GitHub MCP server configuration for Copilot CLI
// Supports both local (Docker) and remote (hosted) modes
func (e *CopilotEngine) renderGitHubCopilotMCPConfig(yaml *strings.Builder, githubTool any, isLast bool) {
githubType := getGitHubType(githubTool)
readOnly := getGitHubReadOnly(githubTool)
toolsets := getGitHubToolsets(githubTool)
allowedTools := getGitHubAllowedTools(githubTool)

yaml.WriteString(" \"github\": {\n")

// Check if remote mode is enabled (type: remote)
if githubType == "remote" {
// Render remote configuration using shared helper
RenderGitHubMCPRemoteConfig(yaml, GitHubMCPRemoteOptions{
ReadOnly: readOnly,
Toolsets: toolsets,
AuthorizationValue: "Bearer \\${GITHUB_PERSONAL_ACCESS_TOKEN}",
IncludeToolsField: true, // Copilot uses tools field
AllowedTools: allowedTools,
IncludeEnvSection: true, // Copilot uses env section for passthrough
})
} else {
// Local mode - use Docker-based GitHub MCP server (default)
githubDockerImageVersion := getGitHubDockerImageVersion(githubTool)
customArgs := getGitHubCustomArgs(githubTool)

RenderGitHubMCPDockerConfig(yaml, GitHubMCPDockerOptions{
ReadOnly: readOnly,
Toolsets: toolsets,
DockerImageVersion: githubDockerImageVersion,
CustomArgs: customArgs,
IncludeTypeField: true, // Copilot includes "type": "local" field
AllowedTools: allowedTools,
EffectiveToken: "", // Copilot uses env passthrough
})
}

if isLast {
yaml.WriteString(" }\n")
} else {
yaml.WriteString(" },\n")
}
}

// renderPlaywrightCopilotMCPConfig generates the Playwright MCP server configuration for Copilot CLI
// Uses the shared helper with Copilot-specific options
func (e *CopilotEngine) renderPlaywrightCopilotMCPConfig(yaml *strings.Builder, playwrightTool any, isLast bool) {
renderPlaywrightMCPConfigWithOptions(yaml, playwrightTool, isLast, true, true)
}

// renderSafeOutputsCopilotMCPConfig generates the Safe Outputs MCP server configuration for Copilot CLI
// Uses the shared helper with Copilot-specific options
func (e *CopilotEngine) renderSafeOutputsCopilotMCPConfig(yaml *strings.Builder, isLast bool) {
renderSafeOutputsMCPConfigWithOptions(yaml, isLast, true)
}

// renderAgenticWorkflowsCopilotMCPConfig generates the Agentic Workflows MCP server configuration for Copilot CLI
// Uses the shared helper with Copilot-specific options
func (e *CopilotEngine) renderAgenticWorkflowsCopilotMCPConfig(yaml *strings.Builder, isLast bool) {
renderAgenticWorkflowsMCPConfigWithOptions(yaml, isLast, true)
}

// renderCopilotMCPConfig generates custom MCP server configuration for Copilot CLI
func (e *CopilotEngine) renderCopilotMCPConfig(yaml *strings.Builder, toolName string, toolConfig map[string]any, isLast bool) error {
// Use the shared renderer with copilot-specific requirements
Expand Down
12 changes: 8 additions & 4 deletions pkg/workflow/copilot_engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -648,8 +648,6 @@ func TestCopilotEngineInstructionPromptNotEscaped(t *testing.T) {
}

func TestCopilotEngineRenderGitHubMCPConfig(t *testing.T) {
engine := NewCopilotEngine()

tests := []struct {
name string
githubTool any
Expand Down Expand Up @@ -717,8 +715,14 @@ func TestCopilotEngineRenderGitHubMCPConfig(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
var yaml strings.Builder
workflowData := &WorkflowData{}
var _ *WorkflowData = workflowData
engine.renderGitHubCopilotMCPConfig(&yaml, tt.githubTool, tt.isLast)
// Use unified renderer instead of direct method call
renderer := NewMCPConfigRenderer(MCPRendererOptions{
IncludeCopilotFields: true,
InlineArgs: true,
Format: "json",
IsLast: tt.isLast,
})
renderer.RenderGitHubMCP(&yaml, tt.githubTool, workflowData)
output := yaml.String()

for _, expected := range tt.expectedStrs {
Expand Down
12 changes: 9 additions & 3 deletions pkg/workflow/copilot_github_mcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,16 @@ func TestRenderGitHubCopilotMCPConfig_AllowedTools(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
engine := NewCopilotEngine()
var output strings.Builder

engine.renderGitHubCopilotMCPConfig(&output, tt.githubTool, tt.isLast)
workflowData := &WorkflowData{}
// Use unified renderer instead of direct method call
renderer := NewMCPConfigRenderer(MCPRendererOptions{
IncludeCopilotFields: true,
InlineArgs: true,
Format: "json",
IsLast: tt.isLast,
})
renderer.RenderGitHubMCP(&output, tt.githubTool, workflowData)
result := output.String()

// Check expected content
Expand Down
11 changes: 9 additions & 2 deletions pkg/workflow/github_toolset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,16 @@ func TestCopilotEngineGitHubToolsetsRendering(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
engine := &CopilotEngine{}
var yaml strings.Builder
engine.renderGitHubCopilotMCPConfig(&yaml, tt.githubTool, true)
workflowData := &WorkflowData{}
// Use unified renderer instead of direct method call
renderer := NewMCPConfigRenderer(MCPRendererOptions{
IncludeCopilotFields: true,
InlineArgs: true,
Format: "json",
IsLast: true,
})
renderer.RenderGitHubMCP(&yaml, tt.githubTool, workflowData)

result := yaml.String()

Expand Down
11 changes: 9 additions & 2 deletions pkg/workflow/mcp_renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,18 @@ func (r *MCPConfigRendererUnified) RenderGitHubMCP(yaml *strings.Builder, github

// Check if remote mode is enabled (type: remote)
if githubType == "remote" {
// Use shell environment variable instead of GitHub Actions expression to prevent template injection
// Determine authorization value based on engine requirements
// Copilot uses MCP passthrough syntax: "Bearer \${GITHUB_PERSONAL_ACCESS_TOKEN}"
// Other engines use shell variable: "Bearer $GITHUB_MCP_SERVER_TOKEN"
authValue := "Bearer $GITHUB_MCP_SERVER_TOKEN"
if r.options.IncludeCopilotFields {
authValue = "Bearer \\${GITHUB_PERSONAL_ACCESS_TOKEN}"
}

RenderGitHubMCPRemoteConfig(yaml, GitHubMCPRemoteOptions{
ReadOnly: readOnly,
Toolsets: toolsets,
AuthorizationValue: "Bearer $GITHUB_MCP_SERVER_TOKEN",
AuthorizationValue: authValue,
IncludeToolsField: r.options.IncludeCopilotFields,
AllowedTools: getGitHubAllowedTools(githubTool),
IncludeEnvSection: r.options.IncludeCopilotFields,
Expand Down
Loading