Skip to main content
Glama
ESPOIR-DITE

git-issuer-mcp

by ESPOIR-DITE
README.md
# 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:

1. **Input validation** — Zod-based schema checks, HTML/script sanitization, base64 payload rejection
2. **Rate limiting** — Sliding-window limiter (default 10 requests/minute)
3. **Repository allowlist** — Only repos listed in `ALLOWED_REPOS` are accepted
4. **GitHub App authentication** — JWT generated from private key, exchanged for a short-lived installation token (cached for 55 minutes)
5. **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).

```bash
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:**

1. Go to **GitHub Settings > Developer settings > GitHub Apps > New GitHub App**
2. Set the following permissions:
   - **Repository permissions > Issues**: Read & Write
3. Under "Where can this GitHub App be installed?", choose **Only on this account** (recommended for internal use)
4. Create the app and note the **App ID** from the app settings page
5. Generate a **private key** (.pem file) — download and store it securely
6. **Install** the app on the repositories you want the agent to access
7. 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`](.env.example) for a documented template.

## Installation

```bash
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.

```bash
npm test
```

This runs the full suite covering:

- **`validation.test.ts`** — Repo format, title/body limits, label constraints, HTML sanitization, base64 detection
- **`auth.test.ts`** — Env validation, .pem file loading, base64 key loading, token caching and refresh
- **`issues.test.ts`** — Allowlist enforcement, successful creation, GitHub API error handling
- **`rateLimiter.test.ts`** — Default/custom limits, per-agent tracking, sliding-window expiry
- **`tools.test.ts`** — End-to-end tool handler flow, error code formatting

To type-check without running tests:

```bash
npm run typecheck
```

## Setting Up in Claude Code

Add the server to your Claude Code MCP configuration at `~/.claude.json`:

```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:

```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"
      }
    }
  }
}
```

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:**

```json
{
  "success": true,
  "issue_number": 42,
  "issue_url": "https://github.com/your-org/repo/issues/42"
}
```

**Error response:**

```json
{
  "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

```bash
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.

TDQS

A3.6/5.0

Scored across 1 tool

Disambiguation5/5

Only one tool exists, so there is no possibility of overlap or confusion. The tool's purpose is clearly stated.

Naming Consistency5/5

The single tool name 'create_issue' follows a clear verb_noun convention, so there is no inconsistency.

Tool Count2/5

With only one tool, the server is severely under-scoped for a GitHub issue workflow. A typical issue lifecycle requires at least create, read, and update operations.

Completeness1/5

The tool only supports issue creation and offers no way to list, read, update, or delete issues. This leaves agents unable to track or manage issues after creation, creating a dead end.

Maintenance

ActivityInactive
ResponsivenessNo issues