TypeScript MCP Server with GitHub Integration
Overview
This template provides a fully production-ready Model Context Protocol (MCP) server built with TypeScript, using HTTP transport and API key authentication. It integrates directly with the GitHub REST API to expose powerful repository management capabilities as MCP tools that Claude and other AI assistants can invoke.
The server is designed for engineering teams who want to give their AI assistants real access to GitHub workflows โ searching repositories, managing issues, reviewing pull requests, and inspecting commit history โ all through a secure, observable, and deployable service. It follows the MCP specification precisely while layering in production concerns like structured logging, health monitoring, and container-based deployment.
Whether you are building an internal developer assistant, automating triage workflows, or exploring AI-native DevOps tooling, this template gives you a solid, extensible foundation. Every component โ authentication middleware, structured logging with Pino, health checks, and Docker configuration โ is wired together and ready to deploy.
What You'll Learn
- How to implement an MCP server using the
@modelcontextprotocol/sdkwith HTTP/SSE transport - How to register and validate MCP tools using Zod schemas with full TypeScript inference
- How to implement API key authentication as Express middleware protecting all MCP endpoints
- How to integrate with the GitHub REST API using Octokit with proper pagination and error handling
- How to set up Pino for structured JSON logging with request correlation IDs and configurable log levels
- How to build a
/healthendpoint that checks each external dependency and returns structured JSON status - How to write a multi-stage Dockerfile that produces a minimal, secure production image
- How to configure docker-compose for local development with live environment variable injection
- How to handle and surface GitHub API errors (rate limits, permissions, not-found) as structured MCP errors
- How to load and validate all configuration from environment variables with typed defaults
- How to structure a TypeScript MCP project for maintainability and extensibility
- How to test MCP tools locally using curl and via Claude Desktop configuration
Architecture
Claude Desktop / MCP Client
โ
โ HTTP POST /mcp (Bearer API Key)
โผ
Express Server
โ
โโโ API Key Auth Middleware (src/auth.ts)
โ โ
โ [401 if invalid]
โ
โโโ Correlation ID Middleware (src/logger.ts)
โ
โโโ MCP HTTP Transport (src/server.ts)
โ โ
โ MCP Protocol Layer (@modelcontextprotocol/sdk)
โ โ
โ Tool Router
โ โโโ github_search_repos
โ โโโ github_list_issues
โ โโโ github_get_pull_request
โ โโโ github_list_commits
โ โ
โ Octokit GitHub Client
โ โ
โ โผ
โ GitHub REST API (api.github.com)
โ
โโโ GET /health (src/health.ts)
โ
Checks: GitHub API reachability
Returns: { status, version, checks, uptime }
Project Structure
github-mcp-server/
โโโ src/
โ โโโ server.ts # Main MCP server, tool registration, Express app
โ โโโ config.ts # Environment variable loading and validation
โ โโโ auth.ts # API key authentication middleware
โ โโโ logger.ts # Pino structured logger with correlation IDs
โ โโโ health.ts # /health endpoint with dependency checks
โโโ Dockerfile # Multi-stage production Docker build
โโโ docker-compose.yml # Local development with env file support
โโโ package.json # Dependencies with pinned versions
โโโ tsconfig.json # TypeScript compiler configuration
โโโ .env.example # All environment variables documented
โโโ .gitignore # TypeScript/Node.js gitignore
Prerequisites
- Node.js 20 or later
- npm 9 or later
- A GitHub Personal Access Token (classic or fine-grained) with
repoandread:orgscopes - Docker and Docker Compose (for containerised development/deployment)
- A Claude Desktop installation (for end-to-end testing)
Quick Start
# 1. Clone or download the template
git clone https://github.com/your-org/github-mcp-server.git
cd github-mcp-server
# 2. Install dependencies
npm install
# 3. Configure environment
cp .env.example .env
# Edit .env โ set GITHUB_TOKEN and MCP_API_KEY at minimum
# 4. Build TypeScript
npm run build
# 5. Start the server
npm start
# Server is now running on http://localhost:3000
# 6. Verify health
curl http://localhost:3000/health
# 7. Test a tool call (replace YOUR_API_KEY)
curl -X POST http://localhost:3000/mcp \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
# --- OR run with Docker Compose ---
docker compose up --build
Features
- 4 GitHub MCP tools: search repositories, list issues, inspect pull requests, view commit history
- API key authentication: every MCP request validated via Bearer token middleware
- Pino structured logging: JSON output, configurable log level, per-request correlation IDs
- Health endpoint:
/healthchecks GitHub API reachability and returns structured JSON - Multi-stage Docker build: build stage compiles TypeScript, production stage runs minimal Node.js Alpine image
- Docker Compose: one-command local development with
.envfile injection - Zod input validation: all tool inputs validated with descriptive error messages before calling GitHub
- GitHub error handling: rate limits, 404s, permission errors surfaced as structured MCP errors
- Graceful shutdown: SIGTERM/SIGINT handlers close connections cleanly
- Typed configuration: all environment variables loaded and typed via
src/config.ts
Works With
- Claude Desktop โ add the server to
claude_desktop_config.jsonusing theurltransport - Claude Code โ use as a remote MCP server with
--mcp-serverflag - Cursor โ configure in Cursor MCP settings with the HTTP endpoint
- Windsurf โ add as an HTTP MCP server in Windsurf plugin configuration
- Continue โ register as an MCP server in
config.jsonundermcpServers
Configuration
| Variable | Description | Required |
|---|---|---|
PORT | HTTP port the server listens on | Optional (default: 3000) |
HOST | Host/interface to bind to | Optional (default: 0.0.0.0) |
MCP_API_KEY | Secret API key clients must send as Bearer token | Required |
GITHUB_TOKEN | GitHub Personal Access Token (classic or fine-grained) | Required |
GITHUB_DEFAULT_ORG | Default GitHub org used when org is not provided in tool call | Optional |
LOG_LEVEL | Pino log level: trace, debug, info, warn, error | Optional (default: info) |
NODE_ENV | Runtime environment: development or production | Optional (default: production) |
GITHUB_API_TIMEOUT_MS | Timeout in ms for GitHub API requests | Optional (default: 10000) |
Deployment
Docker (standalone)
# Build the production image
docker build -t github-mcp-server:latest .
# Run with environment variables
docker run -d \
--name github-mcp-server \
-p 3000:3000 \
-e MCP_API_KEY=your_secret_key \
-e GITHUB_TOKEN=ghp_your_token \
-e LOG_LEVEL=info \
github-mcp-server:latest
# Check logs
docker logs -f github-mcp-server
Docker Compose (local development)
# Start with hot-reload friendly config
docker compose up --build
# Stop
docker compose down
Railway
# Install Railway CLI
npm install -g @railway/cli
# Login and initialise
railway login
railway init
# Set environment variables
railway variables set MCP_API_KEY=your_secret_key
railway variables set GITHUB_TOKEN=ghp_your_token
railway variables set LOG_LEVEL=info
# Deploy
railway up
Fly.io
# Install flyctl and login
curl -L https://fly.io/install.sh | sh
fly auth login
# Launch app (follow prompts, choose the Dockerfile option)
fly launch --name github-mcp-server
# Set secrets
fly secrets set MCP_API_KEY=your_secret_key
fly secrets set GITHUB_TOKEN=ghp_your_token
# Deploy
fly deploy
# Check status
fly status
fly logs
Production Checklist
-
MCP_API_KEYis a cryptographically random string (minimum 32 characters) -
GITHUB_TOKENuses a fine-grained PAT scoped to only the required repositories -
NODE_ENVis set toproductionin all deployment environments -
LOG_LEVELis set toinfoorwarnin production (notdebugortrace) - Health endpoint
/healthis monitored by an uptime service (e.g. UptimeRobot, Better Uptime) - Docker image is built from the multi-stage Dockerfile (not the dev stage)
- Container runs as a non-root user (configured in Dockerfile)
- API key is stored in a secrets manager (Railway secrets, Fly secrets, AWS Secrets Manager)
- GitHub token is rotated on a schedule and has minimal required scopes
- HTTPS/TLS is terminated at the load balancer or reverse proxy in front of this service
- Rate limiting is configured at the ingress level to protect against abuse
- Structured logs are being shipped to a log aggregation platform (Datadog, Loki, CloudWatch)
-
/healthendpoint is NOT protected by API key auth (so monitoring tools can access it) - Graceful shutdown is tested โ server drains in-flight requests before exiting
- Docker image is scanned for vulnerabilities before deployment (e.g.
docker scout, Trivy)
Testing
Manual testing with curl
# List all available tools
curl -s -X POST http://localhost:3000/mcp \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | jq
# Search GitHub repositories
curl -s -X POST http://localhost:3000/mcp \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "github_search_repos",
"arguments": { "query": "mcp server language:typescript", "per_page": 5 }
}
}' | jq
# List open issues for a repository
curl -s -X POST http://localhost:3000/mcp \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "github_list_issues",
"arguments": { "owner": "microsoft", "repo": "vscode", "state": "open", "per_page": 10 }
}
}' | jq
# Verify authentication is enforced (should return 401)
curl -s -o /dev/null -w "%{http_code}" -X POST http://localhost:3000/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
Testing with Claude Desktop
Add the following to your claude_desktop_config.json (found at ~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"github-mcp": {
"url": "http://localhost:3000/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
Restart Claude Desktop. You can then ask Claude: "Search GitHub for TypeScript MCP server examples" or "List the open issues in microsoft/vscode" and Claude will invoke the tools automatically.