A self-hosted Model Context Protocol (MCP) server that integrates Notion and GitHub APIs with enterprise-grade features including OAuth 2.1 authentication, rate limiting, audit logging, multi-tenant support, and Kubernetes deployment.
- OAuth 2.1 Authentication: Secure authentication with PKCE flow for GitHub and Notion
- Rate Limiting: Configurable rate limits per IP and per user (30 requests/min global, 10 writes/min)
- Audit Logging: Comprehensive logging of all tool invocations with Winston
- Multi-Tenant Support: Session-based user isolation with secure token storage
- Health Checks: Kubernetes-ready
/healthzand/readyzendpoints - Metrics: Prometheus-compatible metrics at
/metricsendpoint - Docker & Kubernetes: Production-ready containerization and orchestration
The server exposes:
- Tools: Actions that AI models can invoke (create repositories, search Notion, etc.)
- Resources: Data that AI models can access
- Authentication: Per-user OAuth tokens ensuring proper authorization
Each user's credentials are stored separately, and every request validates the user session before performing operations. This ensures that AI agents never access resources beyond their authorization scope.
- Node.js 20+
- npm or yarn
- Docker (for containerization)
- Kubernetes cluster (for production deployment)
- GitHub OAuth App credentials
- Notion Integration credentials
git clone <repository-url>
cd NotionGithubMCP
npm install- Go to GitHub Settings > Developer settings > OAuth Apps
- Create a new OAuth App
- Set Authorization callback URL:
http://localhost:8080/auth/github/callback - Copy Client ID and Client Secret
- Go to https://www.notion.so/my-integrations
- Create a new integration
- Set Redirect URI:
http://localhost:8080/auth/notion/callback - Copy OAuth Client ID and Client Secret
Copy .env.example to .env and fill in your credentials:
cp .env.example .envEdit .env:
PORT=8080
NODE_ENV=development
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
GITHUB_REDIRECT_URI=http://localhost:8080/auth/github/callback
NOTION_CLIENT_ID=your_notion_client_id
NOTION_CLIENT_SECRET=your_notion_client_secret
NOTION_REDIRECT_URI=http://localhost:8080/auth/notion/callback
SESSION_SECRET=generate_a_random_secret_here
RATE_LIMIT_WINDOW_MS=60000
RATE_LIMIT_MAX_REQUESTS=30
LOG_LEVEL=info# Development mode
npm run dev
# Production build
npm run build
npm startThe server will start at http://localhost:8080
- Visit
http://localhost:8080/dashboard - Click "Connect GitHub" or "Connect Notion"
- Authorize the application
- You'll be redirected back with authentication confirmed
GET /auth/github- Initiate GitHub OAuth flowGET /auth/github/callback- GitHub OAuth callbackGET /auth/notion- Initiate Notion OAuth flowGET /auth/notion/callback- Notion OAuth callbackGET /auth/status- Check authentication statusPOST /auth/logout- Logout
POST /tools/github/repositories- List repositoriesPOST /tools/github/create-repository- Create new repositoryPOST /tools/github/repository- Get repository detailsPOST /tools/github/create-issue- Create issuePOST /tools/github/issues- List issues
POST /tools/notion/search- Search NotionPOST /tools/notion/create-page- Create pagePOST /tools/notion/page- Get page detailsPOST /tools/notion/update-page- Update pagePOST /tools/notion/databases- List databasesPOST /tools/notion/query-database- Query database
GET /healthz- Health checkGET /readyz- Readiness checkGET /metrics- Prometheus metrics
docker build -t notion-github-mcp:latest .docker-compose up -dThis starts:
- MCP Server on port 8080
- Prometheus on port 9090
- Grafana on port 3000
kubectl create secret generic oauth-secrets \
--from-literal=github-client-id=YOUR_GITHUB_CLIENT_ID \
--from-literal=github-client-secret=YOUR_GITHUB_CLIENT_SECRET \
--from-literal=notion-client-id=YOUR_NOTION_CLIENT_ID \
--from-literal=notion-client-secret=YOUR_NOTION_CLIENT_SECRET \
--from-literal=session-secret=YOUR_RANDOM_SESSION_SECRETEdit k8s/configmap.yaml with your domain:
data:
github-redirect-uri: "https://your-domain.com/auth/github/callback"
notion-redirect-uri: "https://your-domain.com/auth/notion/callback"Edit k8s/ingress.yaml with your domain:
spec:
tls:
- hosts:
- your-domain.comkubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/ingress.yaml
kubectl apply -f k8s/network-policy.yamlkubectl get pods
kubectl get services
kubectl get ingress
kubectl logs -f deployment/mcp-serverTo run as an MCP server using stdio transport:
MCP_MODE=stdio npm startThis mode is designed to be used with MCP clients that communicate via standard input/output.
Available metrics:
http_requests_total- Total HTTP requestshttp_request_duration_seconds- Request duration histogramtool_calls_total- Total tool invocationstool_call_duration_seconds- Tool call duration histogramrate_limit_hits_total- Rate limit violations
Access Grafana at http://localhost:3000:
- Default credentials: admin/admin
- Add Prometheus datasource:
http://prometheus:9090 - Create dashboards for metrics visualization
All tool invocations are logged to:
- Console (formatted)
logs/combined.log(all logs)logs/error.log(errors only)
Log format:
{
"timestamp": "2025-10-24T10:00:00Z",
"user": "user@example.com",
"action": "github.create_repository",
"params": {"name": "my-repo"},
"result": "success"
}- Never commit secrets: Use environment variables or Kubernetes secrets
- Enable HTTPS: Always use TLS in production
- Rotate secrets: Regularly rotate OAuth secrets and session keys
- Rate limiting: Adjust limits based on your use case
- Network policies: Restrict egress to required services only
- Token storage: Use encrypted database in production (not in-memory)
- Global: 30 requests/min per IP
- Per-user: 30 requests/min per authenticated user
- Write operations: 10 requests/min per user
Adjust in .env:
RATE_LIMIT_WINDOW_MS=60000
RATE_LIMIT_MAX_REQUESTS=30- Verify OAuth credentials are correct
- Check redirect URIs match exactly
- Ensure callbacks are reachable
- Adjust
RATE_LIMIT_MAX_REQUESTSin environment - Consider per-endpoint limits for your use case
- Check secrets are created:
kubectl get secrets - View logs:
kubectl logs deployment/mcp-server - Verify resource limits are adequate
.
├── src/
│ ├── auth/ # OAuth implementation
│ ├── middleware/ # Rate limiting, metrics
│ ├── mcp/ # MCP server implementation
│ ├── routes/ # Express routes
│ ├── services/ # GitHub and Notion services
│ ├── types/ # TypeScript types
│ ├── utils/ # Logger and utilities
│ └── index.ts # Main application
├── k8s/ # Kubernetes manifests
├── logs/ # Log files
├── Dockerfile # Docker configuration
├── docker-compose.yml # Docker Compose setup
└── package.json # Dependencies
- Add service method in
src/services/github.tsorsrc/services/notion.ts - Register tool in
src/mcp/server.ts(ListToolsRequestSchema handler) - Implement handler in
src/mcp/server.ts(CallToolRequestSchema handler) - Add Express route in
src/routes/tools.ts
MIT
Contributions welcome! Please open an issue or submit a pull request.
For issues and questions, please open a GitHub issue.