github-app-mcp
v1.0.0
Published
MCP server to interact with GitHub via a GitHub App (JWT + installation access token)
Readme
github-app-mcp
A Model Context Protocol (MCP) server that lets an LLM interact with GitHub via a GitHub App — generating short-lived installation access tokens on the fly, then performing git operations and GitHub API calls on behalf of the App.
Requirements
| Dependency | Version |
|---|---|
| Node.js | ≥ 18 (native fetch + crypto) |
| git | any recent version, must be in $PATH |
No external HTTP or JWT library is required. JWT signing uses Node's built-in
crypto module (RS256); HTTP calls use native fetch.
Installation
cd github-app-mcp
npm installBuild
npm run build
# Outputs compiled JS to ./dist/Environment variables
| Variable | Description |
|---|---|
| GITHUB_APP_ID | Numeric App ID shown in your GitHub App settings |
| GITHUB_APP_PRIVATE_KEY | Full PEM content of the private key (newlines can be \n-escaped) |
| GITHUB_APP_INSTALLATION_ID | Installation ID of the App on the target org/user |
Finding the Installation ID
GET https://api.github.com/app/installations(Authenticated with a GitHub App JWT — the server itself calls this internally.)
Running in MCP stdio mode
GITHUB_APP_ID=123456 \
GITHUB_APP_PRIVATE_KEY="$(cat your-app.private-key.pem)" \
GITHUB_APP_INSTALLATION_ID=987654 \
npm startThe server speaks the MCP stdio protocol (JSON-RPC over stdin/stdout).
stderr is used for diagnostics and will not interfere with the protocol.
Claude Desktop / MetaMCP configuration
Add the following block to your claude_desktop_config.json (or equivalent
MCP host config):
{
"mcpServers": {
"github-app": {
"command": "node",
"args": ["/absolute/path/to/github-app-mcp/dist/index.js"],
"env": {
"GITHUB_APP_ID": "123456",
"GITHUB_APP_PRIVATE_KEY": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----",
"GITHUB_APP_INSTALLATION_ID": "987654"
}
}
}
}Tip: The private key value can use literal
\nto encode newlines — the server replaces them automatically.
Available tools
get_installation_token
Generate (or return a cached) GitHub App installation access token.
Input: (none)
Output:
{
"token": "ghs_xxxxxxxxxxxx",
"expires_at": "2025-01-01T12:10:00Z"
}clone_or_update_repo
Clone a repo the first time; fetch + pull on subsequent calls.
Input:
{
"owner": "acme-org",
"repo": "my-service",
"branch": "main",
"localPath": "./workspace/my-service"
}create_branch
Create a new branch in a local repository.
Input:
{
"localPath": "./workspace/my-service",
"branch": "feat/my-feature",
"from": "main"
}commit_and_push
Stage, commit, and push changes.
Input:
{
"localPath": "./workspace/my-service",
"commitMessage": "feat: add new endpoint",
"addPattern": "."
}open_pull_request
Open a pull request on GitHub via the Repos API.
Input:
{
"owner": "acme-org",
"repo": "my-service",
"head": "feat/my-feature",
"base": "main",
"title": "feat: add new endpoint",
"body": "## Summary\n\nAdds `/health` endpoint with JSON response."
}Output:
{
"html_url": "https://github.com/acme-org/my-service/pull/42",
"number": 42,
"state": "open"
}Typical LLM workflow
1. clone_or_update_repo → get a fresh local copy of the repo
2. create_branch → create a feature branch
3. [LLM edits files on disk via other tools / file-system MCP]
4. commit_and_push → stage, commit, push
5. open_pull_request → open the PRToken caching
Installation access tokens are valid for 1 hour. The server caches the current token in memory and only calls the GitHub API again when the token has less than 60 seconds of validity remaining. This avoids unnecessary API calls across consecutive tool invocations in the same session.
Security notes
- The private key is held in memory only for the duration of JWT generation.
- Tokens are injected into git remote URLs using the
x-access-tokenscheme; they are never written to disk (except transiently inside.git/configwhile git is running, which git clears on exit). - Always run this server in a trusted environment — the installation token grants write access to all repositories the App is installed on.
