A Model Context Protocol (MCP) server for GitHub operations, enabling LLMs to interact with GitHub repositories through a standardized interface. Deployable to Cloudflare Workers for production use.
- 🔌 MCP Protocol Compliant: Works with any MCP-compatible LLM (OpenAI, Claude, Cursor, etc.)
- 🚀 9 GitHub Operations: Comprehensive PR management capabilities
- ✅ Type-Safe: Full TypeScript with runtime validation using Zod
- ☁️ Cloudflare Workers Ready: Deploy to the edge with zero configuration
- 🧪 Tested: Golden tests for schema and behavior validation
- 🔐 Secure: GitHub Personal Access Token authentication
- list_prs - List pull requests with filters (state, base, head)
- get_pr - Get detailed PR information
- create_pr - Create new pull request
- update_pr - Update PR title, description, state, or base branch
- add_pr_comment - Add comment to a PR
- merge_pr - Merge PR with specified method (merge/squash/rebase)
- request_pr_reviewers - Request PR reviewers (users or teams)
- add_pr_labels - Add labels to PR
- remove_pr_labels - Remove labels from PR
┌─────────────┐
│     LLM     │  (OpenAI, Claude, Cursor, etc.)
│  (Client)   │
└──────┬──────┘
       │ MCP Protocol
       │ (stdio or HTTP)
       ▼
┌─────────────────┐
│   MCP Server    │
│  (This repo)    │
├─────────────────┤
│ • Tool Registry │
│ • Validation    │
│ • Error Handler │
└──────┬──────────┘
       │
       │ Octokit REST API
       ▼
┌─────────────────┐
│   GitHub API    │
└─────────────────┘
- Node.js 18+
- GitHub Personal Access Token with appropriate permissions
# Clone or create project
npm install
# Set GitHub token
export GITHUB_TOKEN="ghp_your_token_here"
# Build project
npm run build- Go to GitHub Settings → Developer settings → Personal access tokens → Tokens (classic)
- Generate new token with these scopes:
- repo(Full control of private repositories)
- read:org(if working with organization repos)
 
- Copy the token and set as GITHUB_TOKENenvironment variable
Run the MCP server locally using stdio transport:
export GITHUB_TOKEN="your_token"
npm run devThe server will run on stdio and can be connected to by MCP clients.
Run the example test client to see the server in action:
export GITHUB_TOKEN="your_token"
npm run clientThis will:
- Start the MCP server
- Connect via stdio transport
- List available tools
- Execute example operations (list PRs, get PR details)
- Display results
To use with Cursor, add to your MCP configuration:
{
  "mcpServers": {
    "github": {
      "command": "node",
      "args": ["path/to/mcp-deployable/dist/server/index.js"],
      "env": {
        "GITHUB_TOKEN": "your_token_here"
      }
    }
  }
}import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { spawn } from 'child_process';
import OpenAI from 'openai';
// Start MCP server
const serverProcess = spawn('node', ['dist/server/index.js'], {
  env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN },
});
// Connect MCP client
const client = new Client({
  name: 'openai-github-client',
  version: '1.0.0',
}, { capabilities: {} });
const transport = new StdioClientTransport({ command: serverProcess });
await client.connect(transport);
// Get available tools
const tools = await client.listTools();
// Use with OpenAI
const openai = new OpenAI();
const response = await openai.chat.completions.create({
  model: 'gpt-4',
  messages: [
    { role: 'user', content: 'List open PRs in owner/repo' }
  ],
  tools: tools.tools.map(tool => ({
    type: 'function',
    function: {
      name: tool.name,
      description: tool.description,
      parameters: tool.inputSchema,
    },
  })),
});
// Execute tool if requested
if (response.choices[0].message.tool_calls) {
  const toolCall = response.choices[0].message.tool_calls[0];
  const result = await client.callTool({
    name: toolCall.function.name,
    arguments: JSON.parse(toolCall.function.arguments),
  });
  console.log(result);
}- Cloudflare account
- Wrangler CLI installed (npm install -g wrangler)
# Build worker bundle
npm run build:worker
# Set GitHub token as secret
wrangler secret put GITHUB_TOKEN
# Deploy to Cloudflare Workers
npm run deployOnce deployed, the worker exposes these endpoints:
curl https://your-worker.workers.dev/healthcurl https://your-worker.workers.dev/toolscurl -X POST https://your-worker.workers.dev/invoke \
  -H "Content-Type: application/json" \
  -d '{
    "tool": "list_prs",
    "arguments": {
      "owner": "octocat",
      "repo": "hello-world",
      "state": "open"
    }
  }'mcp-deployable/
├── src/
│   ├── server/
│   │   ├── index.ts          # MCP server (stdio)
│   │   ├── tools/
│   │   │   └── github-tools.ts  # GitHub API operations
│   │   └── schemas/
│   │       └── tools.ts      # Zod schemas
│   ├── client/
│   │   └── test-client.ts    # Example client
│   ├── types/
│   │   └── github.ts         # TypeScript types
│   └── worker.ts             # Cloudflare Workers entry
├── tests/
│   └── golden/
│       ├── github-tools.test.ts  # Golden tests
│       └── fixtures/         # Mock responses
├── wrangler.toml             # Workers config
├── package.json
└── tsconfig.json
# Run all tests
npm test
# Run golden tests only
npm run test:golden
# Run tests in watch mode
npm test -- --watch- npm run build- Build TypeScript and worker bundle
- npm run dev- Run MCP server locally (stdio)
- npm run client- Run test client
- npm test- Run test suite
- npm run deploy- Deploy to Cloudflare Workers
{
  "tool": "list_prs",
  "arguments": {
    "owner": "modelcontextprotocol",
    "repo": "typescript-sdk",
    "state": "open",
    "per_page": 10
  }
}{
  "tool": "get_pr",
  "arguments": {
    "owner": "modelcontextprotocol",
    "repo": "typescript-sdk",
    "pull_number": 42
  }
}{
  "tool": "create_pr",
  "arguments": {
    "owner": "myorg",
    "repo": "myrepo",
    "title": "Add new feature",
    "body": "This PR adds feature X",
    "head": "feature-branch",
    "base": "main",
    "draft": false
  }
}{
  "tool": "update_pr",
  "arguments": {
    "owner": "myorg",
    "repo": "myrepo",
    "pull_number": 42,
    "title": "Updated title",
    "body": "Updated description"
  }
}{
  "tool": "add_pr_comment",
  "arguments": {
    "owner": "myorg",
    "repo": "myrepo",
    "pull_number": 42,
    "body": "Great work! LGTM 👍"
  }
}{
  "tool": "merge_pr",
  "arguments": {
    "owner": "myorg",
    "repo": "myrepo",
    "pull_number": 42,
    "merge_method": "squash",
    "commit_title": "feat: add new feature"
  }
}{
  "tool": "request_pr_reviewers",
  "arguments": {
    "owner": "myorg",
    "repo": "myrepo",
    "pull_number": 42,
    "reviewers": ["user1", "user2"],
    "team_reviewers": ["team-alpha"]
  }
}{
  "tool": "add_pr_labels",
  "arguments": {
    "owner": "myorg",
    "repo": "myrepo",
    "pull_number": 42,
    "labels": ["bug", "high-priority"]
  }
}All tools include comprehensive error handling:
- Validation Errors: Input parameters are validated using Zod schemas
- GitHub API Errors: HTTP errors from GitHub API are caught and formatted
- Rate Limiting: GitHub API rate limit errors are properly surfaced
- Authentication Errors: Missing or invalid tokens return clear error messages
Example error response:
{
  "error": "GitHub API error (404): Not Found",
  "code": "INTERNAL_ERROR"
}- Authentication: Uses GitHub Personal Access Token (PAT)
- Least Privilege: Configure tokens with only necessary scopes
- Environment Variables: Secrets stored in env vars, never committed
- Input Validation: All inputs validated before GitHub API calls
MIT
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
For issues or questions:
- Open an issue on GitHub
- Check existing issues for solutions
- Review MCP protocol documentation: https://modelcontextprotocol.io