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
5 changes: 5 additions & 0 deletions .changeset/patch-add-mcp-flag.md

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

11 changes: 11 additions & 0 deletions docs/src/content/docs/tools/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,19 @@ The `init` command prepares your repository for agentic workflows by configuring

```bash
gh aw init
gh aw init --mcp # Configure GitHub Copilot Agent MCP integration
```

**What it does:**
- Configures `.gitattributes` to mark `.lock.yml` files as generated
- Creates GitHub Copilot custom instructions at `.github/instructions/github-agentic-workflows.instructions.md`
- Creates the `/create-agentic-workflow` prompt at `.github/prompts/create-agentic-workflow.prompt.md`

**With `--mcp` flag:**
- Creates `.github/workflows/copilot-setup-steps.yml` with steps to install the gh-aw extension
- Creates `.vscode/mcp.json` with gh-aw MCP server configuration
- Enables the gh-aw MCP server in GitHub Copilot Agent, providing tools like `status`, `compile`, `logs`, and `audit`
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The documentation mentions 'Enables the gh-aw MCP server in GitHub Copilot Agent' but the workflow created is copilot-setup-steps.yml which sets up the environment. Consider clarifying that this configures the environment for the MCP server rather than enabling it directly in the agent.

Suggested change
- Enables the gh-aw MCP server in GitHub Copilot Agent, providing tools like `status`, `compile`, `logs`, and `audit`
- Configures the environment for the gh-aw MCP server in GitHub Copilot Agent, providing tools like `status`, `compile`, `logs`, and `audit`

Copilot uses AI. Check for mistakes.

After initialization, start a chat with an AI agent and use the following prompt to create a new workflow:

```
Expand Down
36 changes: 35 additions & 1 deletion docs/src/content/docs/tools/mcp-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,26 @@ Use the `--port` flag to run the server with HTTP/SSE transport instead of stdio
gh aw mcp-server --port 8080
```

## Configuring with GitHub Copilot Agent

GitHub Copilot Agent can use the gh-aw MCP server in your workflows to manage agentic workflows directly.

**Quick Setup:**

Use the `init` command with the `--mcp` flag to automatically configure GitHub Copilot Agent:

```bash
gh aw init --mcp
```

This creates `.github/workflows/copilot-setup-steps.yml` with:
- Go environment setup
- GitHub CLI installation
- gh-aw extension installation and build from source
- Verification steps

The workflow runs before the Copilot Agent starts, ensuring gh-aw is available as an MCP server in all agent sessions. Once configured, the agent can use tools like `status`, `compile`, `logs`, and `audit` to manage workflows in your repository.

## Configuring with Copilot CLI

The GitHub Copilot CLI can use the gh-aw MCP server to access workflow management tools.
Expand All @@ -76,7 +96,21 @@ This registers the server with Copilot CLI, making workflow management tools ava

VS Code can use the gh-aw MCP server through the Copilot Chat extension.

Create or update `.vscode/mcp.json` in your repository:
**Quick Setup:**

Use the `init` command with the `--mcp` flag to automatically configure VS Code and GitHub Copilot Agent:

```bash
gh aw init --mcp
```

This creates:
- `.vscode/mcp.json` with gh-aw MCP server configuration
- `.github/workflows/copilot-setup-steps.yml` with gh-aw installation steps for GitHub Copilot Agent

**Manual Configuration:**

Alternatively, create or update `.vscode/mcp.json` in your repository:

```json
{
Expand Down
206 changes: 206 additions & 0 deletions pkg/cli/copilot_setup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package cli

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/githubnext/gh-aw/pkg/logger"
"github.com/goccy/go-yaml"
)

var copilotSetupLog = logger.New("cli:copilot_setup")

const copilotSetupStepsYAML = `name: "Copilot Setup Steps"

# This workflow configures the environment for GitHub Copilot Agent with gh-aw MCP server
on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml

jobs:
# The job MUST be called 'copilot-setup-steps' to be recognized by GitHub Copilot Agent
copilot-setup-steps:
runs-on: ubuntu-latest

# Set minimal permissions for setup steps
# Copilot Agent receives its own token with appropriate permissions
permissions:
contents: read

steps:
- name: Checkout code
uses: actions/checkout@v5

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true

- name: Install gh-aw extension
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot always a fresh install so don't check for updates

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplified the extension installation to just gh extension install githubnext/gh-aw in commit 22ff732. Removed the upgrade check since it's always a fresh install in the setup steps.

run: gh extension install githubnext/gh-aw
env:
GH_TOKEN: ${{ github.token }}

- name: Build gh-aw from source
run: |
echo "Building gh-aw from source for latest features..."
make build
continue-on-error: true

- name: Verify gh-aw installation
run: |
gh aw version || ./gh-aw version
`

// WorkflowStep represents a GitHub Actions workflow step
type WorkflowStep struct {
Name string `yaml:"name,omitempty"`
Uses string `yaml:"uses,omitempty"`
Run string `yaml:"run,omitempty"`
With map[string]any `yaml:"with,omitempty"`
Env map[string]any `yaml:"env,omitempty"`
}

// WorkflowJob represents a GitHub Actions workflow job
type WorkflowJob struct {
RunsOn any `yaml:"runs-on,omitempty"`
Permissions map[string]any `yaml:"permissions,omitempty"`
Steps []WorkflowStep `yaml:"steps,omitempty"`
}

// Workflow represents a GitHub Actions workflow file
type Workflow struct {
Name string `yaml:"name,omitempty"`
On any `yaml:"on,omitempty"`
Jobs map[string]WorkflowJob `yaml:"jobs,omitempty"`
}

// ensureCopilotSetupSteps creates or updates .github/workflows/copilot-setup-steps.yml
func ensureCopilotSetupSteps(verbose bool) error {
copilotSetupLog.Print("Creating copilot-setup-steps.yml")

// Create .github/workflows directory if it doesn't exist
workflowsDir := filepath.Join(".github", "workflows")
if err := os.MkdirAll(workflowsDir, 0755); err != nil {
return fmt.Errorf("failed to create workflows directory: %w", err)
}
copilotSetupLog.Printf("Ensured directory exists: %s", workflowsDir)

// Write copilot-setup-steps.yml
setupStepsPath := filepath.Join(workflowsDir, "copilot-setup-steps.yml")

// Check if file already exists
if _, err := os.Stat(setupStepsPath); err == nil {
copilotSetupLog.Printf("File already exists: %s", setupStepsPath)

// Read existing file to check if extension install step exists
content, err := os.ReadFile(setupStepsPath)
if err != nil {
return fmt.Errorf("failed to read existing copilot-setup-steps.yml: %w", err)
}

// Check if the extension install step is already present (quick check)
contentStr := string(content)
if strings.Contains(contentStr, "gh extension install githubnext/gh-aw") ||
strings.Contains(contentStr, "Install gh-aw extension") {
copilotSetupLog.Print("Extension install step already exists, skipping update")
if verbose {
fmt.Fprintf(os.Stderr, "Skipping %s (already has gh-aw extension install step)\n", setupStepsPath)
}
return nil
}

// Parse existing workflow
var workflow Workflow
if err := yaml.Unmarshal(content, &workflow); err != nil {
return fmt.Errorf("failed to parse existing copilot-setup-steps.yml: %w", err)
}

// Inject the extension install step
copilotSetupLog.Print("Injecting extension install step into existing file")
if err := injectExtensionInstallStep(&workflow); err != nil {
return fmt.Errorf("failed to inject extension install step: %w", err)
}

// Marshal back to YAML
updatedContent, err := yaml.Marshal(&workflow)
if err != nil {
return fmt.Errorf("failed to marshal updated workflow: %w", err)
}

if err := os.WriteFile(setupStepsPath, updatedContent, 0644); err != nil {
return fmt.Errorf("failed to update copilot-setup-steps.yml: %w", err)
}
copilotSetupLog.Printf("Updated file with extension install step: %s", setupStepsPath)

if verbose {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot inject extension install step if not found

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added injection logic in commit 22ff732. The function now reads existing copilot-setup-steps.yml files and injects the extension install step after the "Set up Go" step if it's not already present. The injection is idempotent - it won't inject if the step already exists.

fmt.Fprintf(os.Stderr, "Updated %s with gh-aw extension install step\n", setupStepsPath)
}
return nil
}

if err := os.WriteFile(setupStepsPath, []byte(copilotSetupStepsYAML), 0644); err != nil {
return fmt.Errorf("failed to write copilot-setup-steps.yml: %w", err)
}
copilotSetupLog.Printf("Created file: %s", setupStepsPath)

return nil
}

// injectExtensionInstallStep injects the gh-aw extension install step into an existing workflow
func injectExtensionInstallStep(workflow *Workflow) error {
// Define the extension install step to inject
extensionStep := WorkflowStep{
Name: "Install gh-aw extension",
Run: "gh extension install githubnext/gh-aw",
Env: map[string]any{
"GH_TOKEN": "${{ github.token }}",
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The environment variable uses a Go template syntax in a YAML constant that will be marshaled. Consider whether this syntax will be preserved correctly when marshaling the Env map[string]any field. The string literal ${{ github.token }} should remain as-is in the output YAML.

Suggested change
"GH_TOKEN": "${{ github.token }}",
"GH_TOKEN": yaml.String("${{ github.token }}"),

Copilot uses AI. Check for mistakes.
},
}

// Find the copilot-setup-steps job
job, exists := workflow.Jobs["copilot-setup-steps"]
if !exists {
return fmt.Errorf("copilot-setup-steps job not found in workflow")
}

// Find the position to insert the step (after "Set up Go" or after "Checkout code")
insertPosition := -1
for i, step := range job.Steps {
if strings.Contains(step.Name, "Set up Go") {
insertPosition = i + 1
break
}
}

// If Set up Go not found, try after Checkout
if insertPosition == -1 {
for i, step := range job.Steps {
if strings.Contains(step.Name, "Checkout") || strings.Contains(step.Uses, "checkout@") {
insertPosition = i + 1
break
}
}
}

// If still not found, append at the end
if insertPosition == -1 {
insertPosition = len(job.Steps)
}

// Insert the step at the determined position
newSteps := make([]WorkflowStep, 0, len(job.Steps)+1)
newSteps = append(newSteps, job.Steps[:insertPosition]...)
newSteps = append(newSteps, extensionStep)
newSteps = append(newSteps, job.Steps[insertPosition:]...)

job.Steps = newSteps
workflow.Jobs["copilot-setup-steps"] = job

return nil
}
29 changes: 28 additions & 1 deletion pkg/cli/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
var initLog = logger.New("cli:init")

// InitRepository initializes the repository for agentic workflows
func InitRepository(verbose bool) error {
func InitRepository(verbose bool, mcp bool) error {
initLog.Print("Starting repository initialization for agentic workflows")

// Ensure we're in a git repository
Expand Down Expand Up @@ -72,12 +72,39 @@ func InitRepository(verbose bool) error {
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Created getting started guide"))
}

// Configure MCP if requested
if mcp {
initLog.Print("Configuring GitHub Copilot Agent MCP integration")

// Create copilot-setup-steps.yml
if err := ensureCopilotSetupSteps(verbose); err != nil {
initLog.Printf("Failed to create copilot-setup-steps.yml: %v", err)
return fmt.Errorf("failed to create copilot-setup-steps.yml: %w", err)
}
if verbose {
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Created .github/workflows/copilot-setup-steps.yml"))
}

// Create .vscode/mcp.json
if err := ensureMCPConfig(verbose); err != nil {
initLog.Printf("Failed to create MCP config: %v", err)
return fmt.Errorf("failed to create MCP config: %w", err)
}
if verbose {
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Created .vscode/mcp.json"))
}
}

initLog.Print("Repository initialization completed successfully")

// Display success message with next steps
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("✓ Repository initialized for agentic workflows!"))
fmt.Fprintln(os.Stderr, "")
if mcp {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("✓ GitHub Copilot Agent MCP integration configured"))
fmt.Fprintln(os.Stderr, "")
}
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Start a chat and copy the following prompt to create a new workflow:"))
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, " activate @.github/prompts/create-agentic-workflow.prompt.md")
Expand Down
16 changes: 13 additions & 3 deletions pkg/cli/init_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

// NewInitCommand creates the init command
func NewInitCommand() *cobra.Command {
return &cobra.Command{
cmd := &cobra.Command{
Use: "init",
Short: "Initialize repository for agentic workflows",
Long: `Initialize the repository for agentic workflows by configuring .gitattributes and creating GitHub Copilot instruction files.
Expand All @@ -21,20 +21,30 @@ This command:
- Creates GitHub Copilot custom instructions at .github/instructions/github-agentic-workflows.instructions.md
- Creates the /create-agentic-workflow prompt at .github/prompts/create-agentic-workflow.prompt.md

With --mcp flag:
- Creates .github/workflows/copilot-setup-steps.yml with gh-aw installation steps
- Creates .vscode/mcp.json with gh-aw MCP server configuration

After running this command, you can:
- Use GitHub Copilot Chat with /create-agentic-workflow to create workflows interactively
- Add workflows from the catalog with: ` + constants.CLIExtensionPrefix + ` add <workflow-name>
- Create new workflows from scratch with: ` + constants.CLIExtensionPrefix + ` new <workflow-name>

Examples:
` + constants.CLIExtensionPrefix + ` init
` + constants.CLIExtensionPrefix + ` init -v`,
` + constants.CLIExtensionPrefix + ` init -v
` + constants.CLIExtensionPrefix + ` init --mcp`,
Run: func(cmd *cobra.Command, args []string) {
verbose, _ := cmd.Flags().GetBool("verbose")
if err := InitRepository(verbose); err != nil {
mcp, _ := cmd.Flags().GetBool("mcp")
if err := InitRepository(verbose, mcp); err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
os.Exit(1)
}
},
}

cmd.Flags().Bool("mcp", false, "Configure GitHub Copilot Agent MCP server integration")

return cmd
}
Loading
Loading