MCP Scripts
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.
Quick Start
Section titled “Quick Start”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.
HTTP Transport Limits
Section titled “HTTP Transport Limits”Generated workflows create a per-run API key and configure the MCP gateway to send it verbatim in the Authorization header. The HTTP server requires GH_AW_MCP_SCRIPTS_API_KEY at startup and rejects missing or incorrect request credentials with HTTP 401. GET /health and OPTIONS preflight requests remain unauthenticated.
Request bodies are limited to 1 MiB, including chunked uploads. Oversized requests return HTTP 413; malformed JSON returns HTTP 400. Headers and body uploads each have a 30-second timeout. The upload deadline does not limit tool execution time.
Tool Definition
Section titled “Tool Definition”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)Each tool requires description: and exactly one of script:, run:, py:, or go:.
JavaScript Tools (script:)
Section titled “JavaScript Tools (script:)”JavaScript tools wrap your script: in 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 Tools (run:)
Section titled “Shell Tools (run:)”Shell scripts execute in bash with inputs as environment variables (e.g., repo → INPUT_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 (py:)
Section titled “Python Tools (py:)”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. For third-party packages, use the dependencies: field with exact version pins (for example, requests==2.32.3) so gh-aw installs them before first tool invocation.
Go Tools (go:)
Section titled “Go Tools (go:)”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.
Access secrets via os.Getenv("VAR_NAME") (see Environment Variables for the env: field).
Input Parameters
Section titled “Input Parameters”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" args: type: array items: type: string description: "An array of argument strings" choice-param: type: string enum: ["option1", "option2", "option3"] description: "Limited to specific values"Array parameters accept an optional items: { type: string } declaration to
validate every element. Item types support string, number, boolean, array,
and object; only the item type is supported. Omitting items leaves element
types unrestricted. items requires the parameter’s type to be array.
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.
Each JavaScript, shell, Python, or Go invocation receives only its own declared bindings. Two tools can use the same variable name, such as API_KEY, with different secret expressions without overwriting or exposing each other’s values. The compiler gives each binding a distinct server-side transport name; handlers receive the original declared names, not those transport names or the server’s authentication key. Tool bindings are supplied only to the MCP scripts server startup step, not to the agent or gateway environment.
Handlers also retain ordinary runtime variables: PATH, HOME, TMPDIR, TMP, TEMP, LANG, LC_ALL, LC_CTYPE, TZ, GITHUB_WORKSPACE, RUNNER_TEMP, GOROOT, GOPATH, GOCACHE, and GOMODCACHE. Other ambient variables, including job-level credentials and interpreter startup options, are not inherited.
Compatibility change: handlers no longer inherit ambient proxy, TLS, registry, or GitHub metadata configuration. Variables such as HTTPS_PROXY, NODE_EXTRA_CA_CERTS, SSL_CERT_FILE, REQUESTS_CA_BUNDLE, PYTHONPATH, PIP_INDEX_URL, NPM_CONFIG_REGISTRY, GOPROXY, GITHUB_REPOSITORY, GITHUB_SERVER_URL, GITHUB_API_URL, and GITHUB_RUN_ID require explicit entries in each tool’s env: block. For example:
mcp-scripts: corporate-fetch: description: "Read an internal endpoint" script: | return await (await fetch("https://service.example.com/status")).json(); env: NODE_EXTRA_CA_CERTS: "/etc/company-ca.pem" GITHUB_REPOSITORY: "${{ github.repository }}"Dependency installers use a separate allowlist, never tool bindings or server credentials. In addition to ordinary runtime variables, they retain these ambient installer settings:
| Configuration | Variables |
|---|---|
| Proxy | HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY, and their lowercase equivalents |
| Registry | PIP_INDEX_URL, PIP_EXTRA_INDEX_URL, NPM_CONFIG_REGISTRY, npm_config_registry, GOPROXY |
| CA paths | NODE_EXTRA_CA_CERTS, SSL_CERT_FILE, SSL_CERT_DIR, REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE, PIP_CERT, NPM_CONFIG_CAFILE, npm_config_cafile |
Installer URLs must use HTTP or HTTPS (proxies also support SOCKS5 and SOCKS5H), without userinfo, query strings, or fragments. PIP_EXTRA_INDEX_URL accepts space-separated URLs; GOPROXY accepts comma- or pipe-separated URLs and direct/off. Unsafe or malformed settings fail installation with a diagnostic naming only the variable, not its value. All forwarded settings must be non-secret; URL validation cannot identify arbitrary credentials embedded in endpoint paths. Credential-bearing URLs and credential-file overrides such as PIP_CONFIG_FILE or NPM_CONFIG_USERCONFIG are not supported by this environment forwarding mechanism.
Recompile existing workflows to generate the per-tool transport bindings. Environment filtering is not a filesystem or network sandbox: trusted tools still run on the runner host and must not inspect other processes or credential files.
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.mdComplete Example
Section titled “Complete Example”---on: workflow_dispatchengine: copilotimports: - shared/pr-data-mcp-script.mdmcp-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.
MCP script arguments are withheld from server logs, step summaries, and recent-tool-call diagnostics. Output payloads and error details are also withheld from server logs because they may echo arguments. Handlers receive the original inputs, and tool responses remain unchanged. Do not return secrets to the agent.
Troubleshooting
Section titled “Troubleshooting”- Tool Not Found: The tool name the agent calls must exactly match the key under
mcp-scripts:in frontmatter (case-sensitive). Check the agent’s tool list in the run logs against yourmcp-scripts:block. - Script Errors: Check the tool’s error response for details. The
MCP Scriptsstep logs tool names and execution status, but redacts payloads and error details that may contain secrets. - Secret Not Available: Confirm the secret referenced in
env:(e.g.,${{ secrets.API_KEY }}) is defined at the repository or organization level under Settings > Secrets and variables > Actions, and that its name matches exactly. - Large Output: When output exceeds 500 characters, the agent receives a file path instead of inline content (see Large Output Handling); read that file if the agent’s response looks truncated.
Learn More
Section titled “Learn More”- MCP Scripts Specification - Formal W3C-style specification
- Tools - Other tool configuration options
- Imports - Importing shared workflows
- Safe Outputs - Automated post-workflow actions
- MCPs - External MCP server integration
- Custom Safe Output Jobs - Post-workflow custom jobs