git-issuer-mcp
An MCP (Model Context Protocol) server that enables AI agents to create GitHub issues on explicitly allow-listed repositories. It uses GitHub App authentication so the agent never touches tokens directly, and enforces input validation, rate limiting, and repository-level access control.
How It Works
AI Agent → MCP Server (stdio) → GitHub App Auth → GitHub REST API → Repository
The server exposes a single create_issue tool over MCP's stdio transport. When an agent calls it, the request flows through:
- Input validation — Zod-based schema checks, HTML/script sanitization, base64 payload rejection
- Rate limiting — Sliding-window limiter (default 10 requests/minute)
- Repository allowlist — Only repos listed in
ALLOWED_REPOSare accepted - GitHub App authentication — JWT generated from private key, exchanged for a short-lived installation token (cached for 55 minutes)
- Issue creation — Octokit REST client creates the issue and returns the number and URL
All errors are returned as structured JSON with a code and message. Tokens and private keys are never logged or exposed.
Prerequisites
1. Node.js
Node.js 18+ is required (ES2022 target).
node --version # v18.x or higher
2. GitHub App
You need a GitHub App installed on your target organization or account. The app grants the server permission to create issues without sharing personal access tokens with the agent.
Create the app:
- Go to GitHub Settings > Developer settings > GitHub Apps > New GitHub App
- Set the following permissions:
- Repository permissions > Issues: Read & Write
- Under "Where can this GitHub App be installed?", choose Only on this account (recommended for internal use)
- Create the app and note the App ID from the app settings page
- Generate a private key (.pem file) — download and store it securely
- Install the app on the repositories you want the agent to access
- After installation, note the Installation ID (visible in the URL:
https://github.com/settings/installations/<INSTALLATION_ID>)
3. Environment Variables
| Variable | Required | Description | |---|---|---| | GITHUB_APP_ID | Yes | App ID from your GitHub App settings page | | GITHUB_INSTALLATION_ID | Yes | Installation ID from the app installation URL | | GITHUB_PRIVATE_KEY | Yes | Path to the .pem file or the key as a base64-encoded string | | ALLOWED_REPOS | Yes | Comma-separated list of owner/repo entries the agent may target | | RATE_LIMIT_PER_MINUTE | No | Max issue creations per minute (default: 10) |
See .env.example for a documented template.
Installation
git clone <repo-url> git-issuer-mcp
cd git-issuer-mcp
npm install
npm run build
The compiled output lands in dist/.
Running Tests
Tests use Jest with ts-jest and do not require GitHub credentials — all external calls are mocked.
npm test
This runs the full suite covering:
validation.test.ts— Repo format, title/body limits, label constraints, HTML sanitization, base64 detectionauth.test.ts— Env validation, .pem file loading, base64 key loading, token caching and refreshissues.test.ts— Allowlist enforcement, successful creation, GitHub API error handlingrateLimiter.test.ts— Default/custom limits, per-agent tracking, sliding-window expirytools.test.ts— End-to-end tool handler flow, error code formatting
To type-check without running tests:
npm run typecheck
Setting Up in Claude Code
Add the server to your Claude Code MCP configuration at ~/.claude.json:
{
"mcpServers": {
"git-issuer": {
"command": "node",
"args": ["/absolute/path/to/git-issuer-mcp/dist/server.js"],
"env": {
"GITHUB_APP_ID": "123456",
"GITHUB_INSTALLATION_ID": "78901234",
"GITHUB_PRIVATE_KEY": "/absolute/path/to/private-key.pem",
"ALLOWED_REPOS": "your-org/repo-a,your-org/repo-b",
"RATE_LIMIT_PER_MINUTE": "10"
}
}
}
}
Claude Code injects the environment variables into the server process at launch. The agent never sees or controls these values.
After saving the config, restart Claude Code. The create_issue tool will appear in the agent's available tools.
Setting Up in Cursor
Add the server to your Cursor MCP configuration. Open Settings > MCP (or edit .cursor/mcp.json in your project root) and add:
{
"mcpServers": {
"git-issuer": {
"command": "node",
"args": ["/absolute/path/to/git-issuer-mcp/dist/server.js"],
"env": {
"GITHUB_APP_ID": "123456",
"GITHUB_INSTALLATION_ID": "78901234",
"GITHUB_PRIVATE_KEY": "/absolute/path/to/private-key.pem",
"ALLOWED_REPOS": "your-org/repo-a,your-org/repo-b",
"RATE_LIMIT_PER_MINUTE": "10"
}
}
}
}
Restart Cursor after saving. The server will start automatically when the agent invokes the create_issue tool.
MCP Tool Reference
create_issue
Create a GitHub issue on an allowed repository.
Input:
| Field | Type | Required | Constraints | |---|---|---|---| | repo | string | Yes | owner/repo format | | title | string | Yes | 1–200 characters | | body | string | Yes | Max 10,000 characters | | labels | string[] | No | Max 10 labels, each 1–50 characters |
Success response:
{
"success": true,
"issue_number": 42,
"issue_url": "https://github.com/your-org/repo/issues/42"
}
Error response:
{
"success": false,
"error": {
"code": "REPO_NOT_ALLOWED",
"message": "Repository your-org/other-repo is not on the allowlist"
}
}
Error codes:
| Code | Meaning | |---|---| | VALIDATION_ERROR | Input failed schema validation or sanitization | | REPO_NOT_ALLOWED | Repository is not in ALLOWED_REPOS | | RATE_LIMITED | Too many requests within the rate-limit window | | GITHUB_API_ERROR | GitHub API returned an error | | UNAUTHORIZED | GitHub App authentication failed |
Security Model
- Repository allowlist — The agent can only target repos explicitly listed in
ALLOWED_REPOS. Everything else is rejected. - No token exposure — Tokens are generated server-side, cached in memory, and never logged or returned to the agent.
- Input sanitization —
<script>blocks, HTML event attributes, and base64 payloads are stripped or rejected before reaching GitHub. - Rate limiting — A configurable sliding-window limiter prevents runaway issue creation.
- Environment-only config — All secrets are injected via environment variables by the MCP host. The agent cannot modify them at runtime.
Project Structure
src/
├── server.ts # Entry point — registers tools, starts stdio transport
├── mcp/
│ └── tools.ts # Tool schema and handler (validation → rate limit → create)
├── github/
│ ├── auth.ts # GitHub App JWT + installation token with caching
│ └── issues.ts # Issue creation, allowlist enforcement, structured logging
├── security/
│ ├── validation.ts # Zod schema, HTML sanitization, base64 detection
│ └── rateLimiter.ts # Sliding-window per-agent rate limiter
└── __tests__/
├── auth.test.ts
├── issues.test.ts
├── rateLimiter.test.ts
├── tools.test.ts
└── validation.test.ts
Development
npm install # Install dependencies
npm run build # Compile TypeScript to dist/
npm run typecheck # Type-check without emitting
npm test # Run test suite
npm start # Start the server (requires env vars)
License
Internal use only.











