MCP Scripts | GitHub Agentic Workflows

The mcp-scripts: element allows you to define custom MCP (Model Context Protocol) tools directly in your workflow frontmatter using JavaScript, shell scripts, or Python. These tools are generated at runtime and run as an HTTP MCP server on the GitHub Actions runner, outside the agent container. The agent reaches the server via host.docker.internal, keeping tool execution isolated from the AI sandbox while still providing controlled secret access.

mcp-scripts:

greet-user:

description: "Greet a user by name"

inputs:

name:

type: string

required: true

script: |

return { message: `Hello, ${name}!` };

The agent can now call greet-user with a name parameter.

Each mcp-script tool requires a unique name and configuration:

mcp-scripts:

tool-name:

description: "What the tool does" # Required

inputs: # Optional parameters

param1:

type: string

required: true

description: "Parameter description"

param2:

type: number

default: 10

script: | # JavaScript implementation

// Your code here

env: # Environment variables

API_KEY: "${{ secrets.API_KEY }}"

timeout: 120 # Optional: timeout in seconds (default: 60)

  • description: - Human-readable description of what the tool does. This is shown to the agent for tool selection.
  • timeout: - Maximum execution time in seconds (default: 60). The tool will be terminated if it exceeds this duration. Applies to shell (run:) and Python (py:) tools.

Implementation Options

Section titled “Implementation Options”

Choose one implementation method:

  • script: - JavaScript (CommonJS) code
  • run: - Shell script
  • py: - Python script (Python 3.1x)
  • go: - Go (Golang) code

You can only use one of script:, run:, py:, or go: per tool.

JavaScript tools are automatically wrapped in an async function with destructured inputs:

mcp-scripts:

calculate-sum:

description: "Add two numbers"

inputs:

a:

type: number

required: true

b:

type: number

required: true

script: |

const result = a + b;

return { sum: result };

Your script is wrapped as async function execute(inputs) with inputs destructured. Access secrets via process.env:

mcp-scripts:

fetch-data:

description: "Fetch data from API"

inputs:

endpoint:

type: string

required: true

script: |

const apiKey = process.env.API_KEY;

const response = await fetch(`https://api.example.com/${endpoint}`, {

headers: { Authorization: `Bearer ${apiKey}` }

});

return await response.json();

env:

API_KEY: "${{ secrets.API_KEY }}"

Shell scripts execute in bash with inputs as environment variables (e.g., repoINPUT_REPO):

mcp-scripts:

list-prs:

description: "List pull requests"

inputs:

repo:

type: string

required: true

state:

type: string

default: "open"

run: |

gh pr list --repo "$INPUT_REPO" --state "$INPUT_STATE" --json number,title

env:

GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

Shared gh CLI Tool: Import shared/gh.md for a reusable gh tool that accepts any CLI command via args parameter.

Python tools execute using python3 with inputs available as a dictionary. Access inputs via inputs.get('name'), secrets via os.environ, and return results by printing JSON to stdout:

mcp-scripts:

analyze-data:

description: "Analyze data with Python"

inputs:

numbers:

type: string

description: "Comma-separated numbers"

required: true

py: |

import json

numbers_str = inputs.get('numbers', '')

numbers = [float(x.strip()) for x in numbers_str.split(',') if x.strip()]

result = {

"count": len(numbers),

"sum": sum(numbers),

"average": sum(numbers) / len(numbers) if numbers else 0

}

print(json.dumps(result))

Python 3.10+ is available with standard library modules. Install additional packages inline using pip if needed.

Go tools execute using go run with inputs provided as a map[string]any parsed from stdin. Standard library imports (encoding/json, fmt, io, os) are automatically included:

mcp-scripts:

calculate:

description: "Perform calculations with Go"

inputs:

a:

type: number

required: true

b:

type: number

required: true

go: |

a := inputs["a"].(float64)

b := inputs["b"].(float64)

result := map[string]any{

"sum": a + b,

"product": a * b,

}

json.NewEncoder(os.Stdout).Encode(result)

Your Go code receives inputs map[string]any from stdin and should output JSON to stdout. The code is wrapped in a package main with a main() function that handles input parsing.

Available by default:

  • encoding/json - JSON encoding/decoding
  • fmt - Formatted I/O
  • io - I/O primitives
  • os - Operating system functionality

Access environment variables (including secrets) using os.Getenv():

mcp-scripts:

api-call:

description: "Call an API with Go"

inputs:

endpoint:

type: string

required: true

go: |

apiKey := os.Getenv("API_KEY")

endpoint := inputs["endpoint"].(string)

// Make your API call here

result := map[string]any{

"endpoint": endpoint,

"authenticated": apiKey != "",

}

json.NewEncoder(os.Stdout).Encode(result)

env:

API_KEY: "${{ secrets.API_KEY }}"

Define typed parameters with validation:

mcp-scripts:

example-tool:

description: "Example with all input options"

inputs:

required-param:

type: string

required: true

description: "This parameter is required"

optional-param:

type: number

default: 42

description: "This has a default value"

choice-param:

type: string

enum: ["option1", "option2", "option3"]

description: "Limited to specific values"

  • string - Text values
  • number - Numeric values
  • boolean - True/false values
  • array - List of values
  • object - Structured data

Validation Options

Section titled “Validation Options”

  • required: true - Parameter must be provided
  • default: value - Default if not provided
  • enum: [...] - Restrict to specific values
  • description: "..." - Help text for the agent

Timeout Configuration

Section titled “Timeout Configuration”

Set execution timeout with timeout: field (default: 60 seconds):

mcp-scripts:

slow-processing:

description: "Process large dataset"

timeout: 300 # 5 minutes (default: 60)

py: |

import json

import time

time.sleep(120)

print(json.dumps({"status": "complete"}))

Enforced for shell (run:) and Python (py:) tools. JavaScript (script:) tools run in-process without timeout enforcement.

Environment Variables (env:)

Section titled “Environment Variables (env:)”

Pass secrets and configuration via env: (available in JavaScript via process.env, shell via $VAR_NAME):

mcp-scripts:

secure-tool:

description: "Tool with multiple secrets"

script: |

const { API_KEY, API_SECRET } = process.env;

// Use secrets...

env:

API_KEY: "${{ secrets.SERVICE_API_KEY }}"

API_SECRET: "${{ secrets.SERVICE_API_SECRET }}"

Secrets using ${{ secrets.* }} are masked in logs.

Large Output Handling

Section titled “Large Output Handling”

When output exceeds 500 characters, it’s saved to a file. The agent receives the file path, size, and JSON schema preview (if applicable).

Importing MCP Scripts

Section titled “Importing MCP Scripts”

Import tools from shared workflows using imports:. Local tool definitions override imported ones on name conflicts:

imports:

- shared/github-tools.md

---

on: workflow_dispatch

engine: copilot

imports:

- shared/pr-data-mcp-script.md

mcp-scripts:

analyze-text:

description: "Analyze text and return statistics"

inputs:

text:

type: string

required: true

script: |

const words = text.split(/\s+/).filter(w => w.length > 0);

return {

word_count: words.length,

char_count: text.length,

avg_word_length: (text.length / words.length).toFixed(2)

};

safe-outputs:

create-discussion:

category: "General"

---

Analyze provided text using the `analyze-text` tool and create a discussion with results.

Security Considerations

Section titled “Security Considerations”

MCP Scripts tools run on the GitHub Actions runner host — outside the agent container — so they can access the runner’s file system and environment but are isolated from the AI’s own execution environment. Tools also provide secret isolation (only specified env vars are forwarded), process isolation (separate execution), and output sanitization (large outputs saved to files). Only predefined tools are available to agents.

Comparison with Other Options

Section titled “Comparison with Other Options”

FeatureMCP ScriptsCustom MCP ServersBash Tool
SetupInline in frontmatterExternal serviceSimple commands
LanguagesJavaScript, Shell, PythonAny languageShell only
Secret AccessControlled via env:Full accessWorkflow env
IsolationProcess-levelService-levelNone
  • Tool Not Found: Verify tool name matches exactly
  • Script Errors: Check workflow logs for syntax errors
  • Secret Not Available: Confirm secret name in repository/org settings
  • Large Output: Agent reads file path from response