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
8 changes: 7 additions & 1 deletion .github/workflows/scout.lock.yml

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

6 changes: 2 additions & 4 deletions .github/workflows/scout.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@ permissions:
actions: read
roles: [admin, maintainer, write]
engine: copilot
imports:
- shared/tavily-mcp.md
tools:
cache-memory:
retention-days: 7
mcp-servers:
tavily:
url: "https://mcp.tavily.com/mcp/?tavilyApiKey=${{ secrets.TAVILY_API_KEY }}"
allowed: ["*"]
safe-outputs:
add-comment:
max: 1
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/shared/tavily-mcp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
mcp-servers:
tavily:
url: "https://mcp.tavily.com/mcp/?tavilyApiKey=${{ secrets.TAVILY_API_KEY }}"
allowed: ["*"]
---
70 changes: 68 additions & 2 deletions docs/src/content/docs/guides/packaging-imports.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,9 @@ Imports only a specific section from a markdown file using the section header.

### Frontmatter Merging

- **Only `tools:` frontmatter** is allowed in imported files; other entries give a warning
- **Only `tools:` and `mcp-servers:` frontmatter** is allowed in imported files; other entries give a warning
- **Tool merging**: `allowed:` tools are merged across all imported files
- **MCP server merging**: MCP servers defined in imported files are merged with the main workflow

**Example Tool Merging:**

Expand All @@ -326,6 +327,30 @@ tools:

**Result:** Final workflow has `github.allowed: [get_issue, add_issue_comment, update_issue]` and Claude Edit tool.

**Example MCP Server Merging:**

```aw wrap
# Base workflow
---
on: issues
engine: copilot
---

{{#import: shared/tavily-mcp.md}} # Adds Tavily MCP server
```

```aw wrap
# shared/tavily-mcp.md
---
mcp-servers:
tavily:
url: "https://mcp.tavily.com/mcp/?tavilyApiKey=${{ secrets.TAVILY_API_KEY }}"
allowed: ["*"]
---
```

**Result:** Final workflow has the Tavily MCP server configured and available to the AI engine.

### Import Processing During Add

When adding a workflow with the `add` command, local file references in import directives are automatically converted to workflow specifications:
Expand Down Expand Up @@ -353,9 +378,12 @@ The `imports:` field in frontmatter provides an alternative way to import files,
imports:
- shared/common-tools.md
- shared/security-setup.md
- shared/tavily-mcp.md
---
```

Imports can include both tool configurations and MCP server definitions from shared files.

### Import Processing

Like `@include` directives, the imports field is processed during the `add` command:
Expand Down Expand Up @@ -427,6 +455,44 @@ gh aw update experimental

### Example 3: Using Imports for Modular Workflows

Create a shared MCP server configuration:

```aw wrap
# .github/workflows/shared/tavily-mcp.md
---
mcp-servers:
tavily:
url: "https://mcp.tavily.com/mcp/?tavilyApiKey=${{ secrets.TAVILY_API_KEY }}"
allowed: ["*"]
---
```

Create a workflow that imports the MCP server:

```aw wrap
# .github/workflows/research-agent.md
---
on:
issues:
types: [opened]

imports:
- shared/tavily-mcp.md

tools:
github:
allowed: [add_issue_comment]
---

# Research Agent

Perform web research using Tavily and respond to issues.
```

The final workflow will have both the Tavily MCP server and GitHub tools configured.

### Example 4: Using Imports for Common Tools

Create a base workflow with common tools:

```aw wrap
Expand Down Expand Up @@ -466,7 +532,7 @@ Handle new issues with automated responses.

The final workflow will have all three GitHub tools: `get_issue`, `get_pull_request`, and `add_issue_comment`.

### Example 4: Creating Reusable Components
### Example 5: Creating Reusable Components

**Shared security notice:**

Expand Down
27 changes: 26 additions & 1 deletion docs/src/content/docs/reference/include-directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ Imports only a specific section from a markdown file using the section header.

## Frontmatter Merging

- **Only `tools:` frontmatter** is allowed in imported files, other entries give a warning.
- **Only `tools:` and `mcp-servers:` frontmatter** is allowed in imported files, other entries give a warning.
- **Tool merging**: `allowed:` tools are merged across all imported files
- **MCP server merging**: MCP servers defined in imported files are merged with the main workflow

### Example Tool Merging
```aw wrap
Expand All @@ -64,6 +65,30 @@ tools:

**Result**: Final workflow has `github.allowed: [get_issue, add_issue_comment, update_issue]` and Claude Edit tool.

### Example MCP Server Merging

```aw wrap
# Base workflow
---
on: issues
engine: copilot
---

@import shared/tavily-mcp.md # Adds Tavily MCP server
```

```aw wrap
# shared/tavily-mcp.md
---
mcp-servers:
tavily:
url: "https://mcp.tavily.com/mcp/?tavilyApiKey=${{ secrets.TAVILY_API_KEY }}"
allowed: ["*"]
---
```

**Result**: Final workflow has the Tavily MCP server configured and available to the AI engine.

## Import Path Resolution

- **Relative paths**: Resolved relative to the importing file
Expand Down
47 changes: 39 additions & 8 deletions pkg/parser/frontmatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,11 @@ type FrontmatterResult struct {

// ImportsResult holds the result of processing imports from frontmatter
type ImportsResult struct {
MergedTools string // Merged tools configuration from all imports
MergedEngines []string // Merged engine configurations from all imports
MergedMarkdown string // Merged markdown content from all imports
ImportedFiles []string // List of imported file paths (for manifest)
MergedTools string // Merged tools configuration from all imports
MergedMCPServers string // Merged mcp-servers configuration from all imports
MergedEngines []string // Merged engine configurations from all imports
MergedMarkdown string // Merged markdown content from all imports
ImportedFiles []string // List of imported file paths (for manifest)
}

// ExtractFrontmatterFromContent parses YAML frontmatter from markdown content string
Expand Down Expand Up @@ -387,6 +388,7 @@ func ProcessImportsFromFrontmatterWithManifest(frontmatter map[string]any, baseD

// Process each import
var toolsBuilder strings.Builder
var mcpServersBuilder strings.Builder
var markdownBuilder strings.Builder
var engines []string
var processedFiles []string
Expand Down Expand Up @@ -451,13 +453,20 @@ func ProcessImportsFromFrontmatterWithManifest(frontmatter map[string]any, baseD
if err == nil && engineContent != "" {
engines = append(engines, engineContent)
}

// Extract mcp-servers from imported file
mcpServersContent, err := extractMCPServersFromContent(string(content))
if err == nil && mcpServersContent != "" && mcpServersContent != "{}" {
mcpServersBuilder.WriteString(mcpServersContent + "\n")
}
}

return &ImportsResult{
MergedTools: toolsBuilder.String(),
MergedEngines: engines,
MergedMarkdown: markdownBuilder.String(),
ImportedFiles: processedFiles,
MergedTools: toolsBuilder.String(),
MergedMCPServers: mcpServersBuilder.String(),
MergedEngines: engines,
MergedMarkdown: markdownBuilder.String(),
ImportedFiles: processedFiles,
}, nil
}

Expand Down Expand Up @@ -810,6 +819,28 @@ func extractToolsFromContent(content string) (string, error) {
return strings.TrimSpace(string(toolsJSON)), nil
}

// extractMCPServersFromContent extracts mcp-servers section from frontmatter as JSON string
func extractMCPServersFromContent(content string) (string, error) {
result, err := ExtractFrontmatterFromContent(content)
if err != nil {
return "{}", nil // Return empty object on error
}

// Extract mcp-servers section
mcpServers, exists := result.Frontmatter["mcp-servers"]
if !exists {
return "{}", nil
}

// Convert to JSON string
mcpServersJSON, err := json.Marshal(mcpServers)
if err != nil {
return "{}", nil
}

return strings.TrimSpace(string(mcpServersJSON)), nil
}

// extractEngineFromContent extracts engine section from frontmatter as JSON string
func extractEngineFromContent(content string) (string, error) {
result, err := ExtractFrontmatterFromContent(content)
Expand Down
Loading