# How to Configure MCP Servers in Workflow Frontmatter with Version Pinning

> Configure MCP server versions in GitHub Agentic Workflows using frontmatter for reproducible builds. Learn version pinning for stdio, container, and http servers.

- Repository: [GitHub/gh-aw](https://github.com/github/gh-aw)
- Tags: how-to-guide
- Published: 2026-02-16

---

**Pin MCP server versions in GitHub Agentic Workflows by adding a `version` field to the tool configuration in your frontmatter, ensuring reproducible builds across `stdio`, `container`, and `http` server types.**

GitHub Agentic Workflows (repository `github/gh-aw`) enable developers to integrate custom Model Context Protocol (MCP) servers directly into automated workflows. When you configure MCP servers in workflow frontmatter, **version pinning** is essential to guarantee that the same server binary or container image is used across every execution, preventing silent breaking changes from upstream updates.

## Understanding MCP Server Configuration in Frontmatter

Workflow frontmatter in `github/gh-aw` uses a structured `tools:` section to declare external capabilities. The system parses these declarations into internal configuration objects that drive the MCP server lifecycle.

### The ToolsConfig Structure

In [`pkg/workflow/tools_types.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/tools_types.go), the `ToolsConfig` struct defines how custom MCP servers are stored. The `Custom` map holds per-tool configurations using `MCPServerConfig` values:

```go
// pkg/workflow/tools_types.go L81-L84
type ToolsConfig struct {
    // ... other fields ...
    Custom map[string]MCPServerConfig `yaml:"custom,omitempty"`
}

```

This structure captures the `version` field alongside connection parameters, ensuring the pinned value travels through the entire compilation pipeline.

### Where Version Pinning is Defined

The frontmatter parser in [`pkg/workflow/tools_parser.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/tools_parser.go) extracts the `version` key from the raw YAML map and normalizes it into the configuration object. Lines 190-299 and 365-404 handle the extraction logic, supporting string, integer, and float representations of version numbers before converting them to canonical string form for the workflow compiler.

## How to Pin MCP Server Versions in Workflow Frontmatter

Version pinning syntax varies slightly depending on the MCP server transport type. All configurations reside under the `tools:` key in your workflow's frontmatter block.

### STDIO-Based MCP Servers

For servers that communicate via standard input/output, specify the `version` alongside the `command` and `args`:

```yaml
---
tools:
  my-analysis-server:
    type: stdio
    command: "node"
    args: ["/usr/local/lib/mcp-server/index.js"]
    version: "v1.41.0"  # Pin to exact release

---

```

The `version` field accepts semantic version strings, commit SHAs, or any identifier your deployment system recognizes.

### Container-Based MCP Servers

Container-type servers **require** version pinning according to the validation logic in [`pkg/workflow/mcp_config_validation.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/mcp_config_validation.go) (lines 270-282). The compiler enforces that `container` type tools include a `version` field to prevent accidental "latest" tag usage:

```yaml
---
tools:
  secure-container-tool:
    type: container
    container: "ghcr.io/example/mcp-security-scanner"
    version: "sha-09deac4"  # Immutable digest preferred

    env:
      SCAN_DEPTH: "deep"
---

```

If you omit `version` for a container tool, the validator returns an error: `tool 'X' mcp configuration must specify a 'version' field`.

### HTTP-Based MCP Servers

HTTP transport optionally supports version pinning for documentation purposes, though the remote server controls the actual implementation:

```yaml
---
tools:
  remote-api-bridge:
    type: http
    url: "https://api.example.com/mcp/v1"
    version: "v2.0"  # Informational; verify remote compatibility

---

```

## Validation and Enforcement

The `github/gh-aw` compiler implements strict validation to ensure version pinning compliance. In [`pkg/workflow/mcp_config_validation.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/mcp_config_validation.go), the validation logic specifically checks container-based configurations:

```go
// pkg/workflow/mcp_config_validation.go L270-L282
if config.Type == "container" && config.Version == "" {
    return fmt.Errorf("tool '%s' mcp configuration must specify a 'version' field", toolName)
}

```

This enforcement guarantees that containerized MCP servers cannot execute with floating tags like `latest`, preventing supply-chain attacks and breaking changes from upstream image updates.

## Complete Workflow Frontmatter Example

Here is a production-ready frontmatter block demonstrating multiple MCP server types with strict version pinning:

```yaml
---
engine: copilot
tools:
  # Built-in GitHub toolset (managed by GitHub, version controlled separately)

  github:
    toolsets: [default]

  # Custom stdio server for data analysis

  data-processor:
    type: stdio
    command: "python3"
    args: ["/opt/mcp-servers/data-processor/main.py"]
    version: "v3.2.1"

  # Container-based security scanner with immutable digest

  security-audit:
    type: container
    container: "ghcr.io/company/mcp-security-scanner"
    version: "sha-256:09deac4f2b3c8e5d6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9"
    env:
      SEVERITY_THRESHOLD: "high"
      OUTPUT_FORMAT: "sarif"
---

```

## Summary

- **Version pinning** in `github/gh-aw` workflow frontmatter ensures MCP server reproducibility by locking the specific binary or container image used during execution.
- **Configuration location**: The `ToolsConfig` struct in [`pkg/workflow/tools_types.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/tools_types.go) stores versions in the `Custom` map, parsed by [`pkg/workflow/tools_parser.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/tools_parser.go).
- **Container enforcement**: [`pkg/workflow/mcp_config_validation.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/mcp_config_validation.go) mandates version fields for container-type servers to prevent floating tag usage.
- **Syntax flexibility**: Versions accept semantic tags, Git SHAs, or Docker digests depending on the server type (`stdio`, `container`, or `http`).

## Frequently Asked Questions

### Why is version pinning required for container-based MCP servers?

Container-based MCP servers in `github/gh-aw` require version pinning to prevent supply-chain attacks and breaking changes from mutable image tags. The validator in [`pkg/workflow/mcp_config_validation.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/mcp_config_validation.go) (lines 270-282) explicitly rejects container configurations lacking a `version` field, ensuring workflows use immutable references like SHA digests rather than floating `latest` tags.

### Can I use semantic versioning or Docker digests for version pinning?

Yes, the `version` field accepts any string identifier your deployment infrastructure recognizes. For `stdio` servers, use semantic versions like `v1.41.0` or Git commit SHAs. For `container` servers, Docker image digests (e.g., `sha-09deac4` or full SHA-256 hashes) provide the strongest immutability guarantees, though semantic tags are also valid if your registry supports immutable tagging.

### What happens if I omit the version field for a stdio MCP server?

Unlike container servers, `stdio` MCP servers do not enforce version presence in the current `github/gh-aw` implementation. However, omitting the version risks executing different binary versions across workflow runs if the underlying command path points to a mutable installation. Best practice dictates always specifying a `version` field for documentation and reproducibility, even when not strictly enforced by [`pkg/workflow/mcp_config_validation.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/mcp_config_validation.go).

### How does the frontmatter parser handle version formats?

The frontmatter parser in [`pkg/workflow/tools_parser.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/tools_parser.go) (lines 190-299 and 365-404) normalizes the `version` value into a canonical string representation. It accepts YAML strings, integers, and floats, converting numeric values to strings (e.g., `1.41` becomes `"1.41"`). This normalized string is then stored in the `MCPServerConfig.Version` field defined in [`pkg/workflow/tools_types.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/tools_types.go), ensuring consistent handling regardless of how the user formats the frontmatter value.