gh-attach
Enables AI applications to upload images and videos to GitHub issues, pull requests, and comments through the Model Context Protocol.
README
gh-attach
Upload images and videos to GitHub issues, PRs, and comments — from the CLI or via MCP.
<p align="center"> <img src="demo.svg" alt="gh-attach CLI demo" width="700"> </p>
GitHub doesn't provide an official API for comment attachments on issues and pull requests. gh-attach fills this gap with multiple upload strategies, a clean CLI, and an MCP server for AI-powered workflows.
Features
- Multiple upload strategies — browser session, cookie extraction, release assets (official API), repo-branch fallback
- Images + videos — PNG, GIF, JPEG, SVG, WebP, MP4, MOV, and WEBM
- CLI tool — works standalone or as a
ghextension (gh attach) - MCP server — expose upload capabilities to AI applications via Model Context Protocol
- Fully tested — unit, integration, and E2E test suites
- Automated releases — semantic versioning with conventional commits
Install
For most users, install from the public npm registry — no npm authentication is required.
Standalone CLI (npm)
# Install globally from public npm
npm install -g gh-attach
Run it as gh-attach ....
Optional: GitHub Packages mirror
# Install the scoped mirror from GitHub Packages (requires GitHub Packages auth)
npm install -g @addono/gh-attach --registry=https://npm.pkg.github.com
GitHub CLI extension
gh extension install Addono/gh-attach
Run it as gh attach ....
Standalone release binary
Download the matching asset from the latest release and place it on your PATH. Release assets are published as:
gh-attach-darwin-arm64gh-attach-darwin-amd64gh-attach-linux-amd64gh-attach-windows-amd64.exe
Run it as gh-attach ....
Run without installing (npx)
# Upload a file
npx -y gh-attach@latest upload ./screenshot.png --target owner/repo#42
# Start the MCP server
npx -y gh-attach@latest mcp --transport stdio
Keeping gh-attach up to date
# npm install
npm install -g gh-attach@latest
# gh extension install
gh extension upgrade Addono/gh-attach
If you run via npx, there is nothing to upgrade locally — each invocation resolves gh-attach@latest. Pin a specific version instead if you do not want the latest release:
npx -y gh-attach@<version> mcp --transport stdio
If you installed a standalone release binary, download the newest matching asset from the latest GitHub release and replace your existing gh-attach executable.
Verify the active version with gh-attach --version or gh attach --version, depending on how you installed it.
Quick Start
If you installed gh-attach as a GitHub CLI extension, replace gh-attach with gh attach in the examples below.
# Upload a file to an issue
gh-attach upload ./screenshot.png --target owner/repo#42
# Upload using the release-asset strategy (official API, works with tokens)
gh-attach upload ./diagram.png --target #42 --strategy release-asset
# Get just the URL
gh-attach upload ./img.png --target #42 --format url
# JSON output
gh-attach upload ./img.png --target #42 --format json
Videos (.mp4, .mov, .webm) are emitted as bare URLs in markdown output so GitHub can render them inline when the target upload URL supports video playback.
Authentication
Strategy 1: Browser Session (default)
gh-attach login # Opens browser, saves session cookies
Strategy 2: Release Assets (official API)
export GITHUB_TOKEN=ghp_... # or GH_TOKEN
gh-attach upload ./img.png --target #42 --strategy release-asset
If neither GITHUB_TOKEN nor GH_TOKEN is set, gh-attach automatically falls back to a token from the GitHub CLI (gh auth token) — so an authenticated gh auth login session is enough. The lookup order for the API token is:
GITHUB_TOKENenvironment variableGH_TOKENenvironment variable- GitHub CLI stored credentials (
gh auth token) — when multiple accounts are signed in, the one most likely to have access to the target repository is preferred
This applies to every code path that needs an API token (the release-asset and repo-branch strategies, in both the CLI and the MCP server).
Strategy 3: Cookie Extraction
Automatically extracts GitHub cookies from Chrome/Firefox.
Strategy 4: Repository Branch
Commits attachments to an orphan branch. Works with any token.
MCP Server
Choose the MCP command that matches how you installed gh-attach:
| Install method | MCP command |
|---|---|
| Standalone npm install | gh-attach mcp --transport stdio |
| Standalone release binary | gh-attach mcp --transport stdio |
gh extension |
gh attach mcp --transport stdio |
npx |
npx -y gh-attach@latest mcp --transport stdio |
When the MCP client supports elicitation, upload_image can prompt for a GitHub token during the same tool call and continue the upload without requiring a separate login step first.
# stdio transport (standalone install or release binary)
gh-attach mcp --transport stdio
# stdio transport (gh extension)
gh attach mcp --transport stdio
# HTTP transport
gh-attach mcp --transport http --port 3000
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
Standalone CLI or release binary
{
"mcpServers": {
"gh-attach": {
"command": "gh-attach",
"args": ["mcp", "--transport", "stdio"]
}
}
}
GitHub CLI extension
{
"mcpServers": {
"gh-attach": {
"command": "bash",
"args": [
"-lc",
"export GITHUB_TOKEN=\"$(gh auth token)\" && exec gh attach mcp --transport stdio"
]
}
}
}
This wrapper requires bash and an authenticated GitHub CLI session (gh auth login). It resolves the token at startup instead of storing it in the config file, but the token is still present in the MCP server process environment while it is running. If bash is unavailable, use the standalone CLI setup instead.
VS Code / GitHub Copilot
Add to .vscode/settings.json:
Standalone CLI or release binary
{
"mcp": {
"servers": {
"gh-attach": {
"type": "local",
"command": "gh-attach",
"args": ["mcp", "--transport", "stdio"],
"tools": ["*"]
}
}
}
}
GitHub CLI extension
{
"mcp": {
"servers": {
"gh-attach": {
"type": "local",
"command": "bash",
"args": [
"-lc",
"export GITHUB_TOKEN=\"$(gh auth token)\" && exec gh attach mcp --transport stdio"
],
"tools": ["*"]
}
}
}
}
This wrapper requires bash and an authenticated GitHub CLI session (gh auth login). It resolves the token at startup instead of storing it in the config file, but the token is still present in the MCP server process environment while it is running. If bash is unavailable, use the standalone CLI setup instead.
If you prefer npx, use command: "npx" and prepend -y, gh-attach@latest to the args array.
Configuration
gh-attach config set strategy-order "release-asset,browser-session"
gh-attach config set default-target owner/repo
gh-attach config list
gh-attach config get default-target
Config is stored at ~/.config/gh-attach/config.json (overridable via GH_ATTACH_CONFIG or XDG_CONFIG_HOME).
Environment Variables
| Variable | Description |
|---|---|
GITHUB_TOKEN / GH_TOKEN |
GitHub API token for release-asset and repo-branch strategies. When unset, falls back to gh auth token from the GitHub CLI. |
GH_ATTACH_COOKIES |
Session cookies for browser-session strategy |
GH_ATTACH_STRATEGY |
Override default strategy selection |
GH_ATTACH_STATE_PATH |
Override session state file location |
GH_ATTACH_CONFIG |
Override config file location |
NO_COLOR |
Disable ANSI color codes in output |
Exit Codes
| Code | Meaning |
|---|---|
0 |
Success |
1 |
General error |
2 |
Authentication error |
3 |
Validation error (bad input) |
4 |
Network/upload error |
Programmatic Usage
import { upload, selectStrategy } from "gh-attach";
const strategy = await selectStrategy({ token: process.env.GITHUB_TOKEN });
const result = await strategy.upload({
file: "./screenshot.png",
target: { owner: "octocat", repo: "hello-world", issue: 42 },
});
console.log(result.url); // https://github.com/user-attachments/assets/...
Development
npm install
npm run build # Build with tsup
npm test # Unit + integration tests
npm run test:e2e # E2E tests (requires secrets)
npm run typecheck # TypeScript strict mode
npm run lint # ESLint
Release automation
- Public npm releases publish the unscoped package as
gh-attach. - GitHub Packages keeps a scoped mirror at
@addono/gh-attach. - GitHub Actions publishes to npm via Trusted Publishing (OIDC), so the release workflow does not need an
NPM_TOKENrepository secret. - Configure npm trusted publishing for package
gh-attachwith:- Organization or user:
Addono - Repository:
gh-attach - Workflow filename:
release.yml - Environment name: leave empty unless you later protect releases with a GitHub Actions environment
- Organization or user:
- After the first trusted publish succeeds, npm recommends enabling Require two-factor authentication and disallow tokens in the package publishing access settings.
Branch Protection (Recommended)
For production repositories, configure the following protections on the main branch via Settings → Branches → Branch protection rules:
| Setting | Value |
|---|---|
| Require a pull request before merging | ✅ enabled |
| Require approvals | 1 review |
| Require status checks to pass | ✅ enabled |
| Required status checks | Lint & Format, Typecheck, Build, and the Test (...) matrix jobs you want to enforce (for example Test (Node 22, ubuntu-latest) and Test (Node 24, ubuntu-latest)) |
| Require branches to be up to date | ✅ enabled |
| Require conversation resolution | ✅ enabled |
| Require linear history | ✅ enabled |
| Do not allow bypassing the above settings | ✅ enabled |
To configure via the GitHub CLI:
gh api repos/{owner}/{repo}/branches/main/protection \
--method PUT \
--field required_status_checks='{"strict":true,"checks":[{"context":"Lint & Format"},{"context":"Typecheck"},{"context":"Build"},{"context":"Test (Node 22, ubuntu-latest)"},{"context":"Test (Node 24, ubuntu-latest)"}]}' \
--field enforce_admins=true \
--field required_pull_request_reviews='{"required_approving_review_count":1}' \
--field restrictions=null
Specifications
See openspec/specs/ for the full OpenSpec specifications:
License
MIT
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.