Add OAuth 2.1 to Your MCP Server: The GitHub Connector Pattern in 2026
- Understand which four OAuth 2.1 endpoints a spec-compliant MCP server must expose
- Implement a GitHub OAuth proxy pattern in an MCP server using Cloudflare Workers OAuthProvider
- Identify the three compliance gaps that block production MCP servers from connecting to Claude.ai
A production MCP server that connects to GitHub via Claude.ai needs four OAuth 2.1 endpoints: /.well-known/oauth-authorization-server, /register (Dynamic Client Registration, RFC 7591), /authorize, and /token. You proxy GitHub's OAuth flow to issue your own bearer tokens. Without Dynamic Client Registration, Claude.ai's connector refuses to connect — which is exactly why GitHub's own remote MCP server cannot connect to Claude.ai today without implementing fake authorization endpoints.
Here is what compliant OAuth 2.1 for an MCP server that calls GitHub actually looks like — with runnable code and the exact compliance checklist that catches 91.5% of teams before production.
GitHub's MCP server is the cautionary tale, not the example
The MCP spec's authorization page requires that MCP clients and authorization servers SHOULD support Dynamic Client Registration (RFC 7591). Claude.ai's connector flow requires it: when you add a custom connector, Claude.ai POSTs to your /register endpoint to auto-register itself. No /register, no connection.
GitHub's remote MCP server at https://api.githubcopilot.com/mcp/ does not support DCR. FastMCP issue #3085 captures the precise complaint: "Some MCP servers (e.g., GitHub Copilot's MCP server) do not support DCR and require clients to be manually registered or use static credentials." The thread links to GitHub's own acknowledgment that DCR "will come" but isn't shipped.
The result is illustrated in Claude Code issue #2527: teams connecting Azure AD-backed MCP servers to Claude.ai had to implement a fake authorization server with a hard-coded /register endpoint that returns static client credentials, a mock /.well-known/oauth-authorization-server document, and a /token proxy that speaks Azure AD's actual flow. GitHub's server forces the same workaround.
This is not a niche edge case. As of March 2026, only 8.5% of servers in the official MCP registry implement OAuth at all. The other 91.5% use static api-keys or no authentication. The compliance gap is not an oversight — it is a tooling problem. No framework makes OAuth as simple as pasting a token into a config file. Until now.
What OAuth 2.1 for MCP requires: the four-endpoint contract
The mcp spec mandates this for any remote server:
| Endpoint | RFC | Purpose |
|---|---|---|
/.well-known/oauth-authorization-server | RFC 8414 | Authorization Server Metadata — discovery document |
/register | RFC 7591 | Dynamic Client Registration — auto-register MCP clients |
/authorize | OAuth 2.1 | Authorization Code Flow with PKCE (S256 required) |
/token | OAuth 2.1 | Token exchange; returns bearer token to MCP client |
Two additional requirements from the 2026-07-28 spec RC (locked May 21, 2026):
- RFC 9207 `iss` validation: Clients must validate the
issparameter on authorization responses. This closes mix-up attacks — where an attacker routes an authorization code from one server to a client expecting another. - RFC 8707 Resource Indicators: Tokens must be audience-bound to the specific MCP server. A token issued for
github-mcp.example.commust not be accepted bydocs-mcp.example.com.
The MCP server acts as an OAuth Resource Server to the MCP client, and as an OAuth Client to GitHub. You issue your own short-lived bearer tokens to the MCP client; GitHub tokens stay server-side. This double-layer is by design — it prevents token leakage and isolates GitHub's scopes from MCP scope management.
The GitHub connector pattern in practice
Your MCP server sits between the MCP client (Claude, Cursor, VS Code) and GitHub's API. The flow has two legs:
``
MCP Client → [your MCP server OAuth 2.1 endpoints] → GitHub OAuth App → GitHub API
``
Step 1: Create a GitHub OAuth App (Settings → Developer Settings → OAuth Apps). Set the callback URL to https://your-mcp-server.example.com/callback. GitHub only supports one callback URL per app, so use separate apps for dev and prod.
Step 2: Expose the four required endpoints. The Cloudflare Workers `OAuthProvider` class handles all four in ~50 lines:
```typescript import { OAuthProvider } from "@cloudflare/workers-oauth-provider"; import { McpAgent } from "agents/mcp";
export default new OAuthProvider({ apiRoute: "/mcp", apiHandler: MyGitHubMcpServer.serve("/mcp"), defaultHandler: GitHubAuthHandler, // handles /authorize and /callback authorizeEndpoint: "/authorize", tokenEndpoint: "/token", clientRegistrationEndpoint: "/register", }); ```
The GitHubAuthHandler proxies to github.com/login/oauth/authorize, exchanges the code for a GitHub token at /callback, then issues its own opaque bearer token to the MCP client. The GitHub token is stored server-side in a KV binding — the MCP client never sees it.
Step 3: Write the Authorization Server Metadata document. This is what the MCP client fetches first:
``json
// GET /.well-known/oauth-authorization-server
{
"issuer": "https://your-mcp-server.example.com",
"authorization_endpoint": "https://your-mcp-server.example.com/authorize",
"token_endpoint": "https://your-mcp-server.example.com/token",
"registration_endpoint": "https://your-mcp-server.example.com/register",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code"],
"code_challenge_methods_supported": ["S256"]
}
``
Missing the registration_endpoint field is the single most common reason Claude.ai's connector falls back to "Authorization failed" after a successful OAuth dance — the client discovers that DCR isn't available and aborts.
Compliance table: what production MCP servers actually support
Original data collected from public GitHub issues, docs, and MCP spec against three prominent production servers (June 2026):
| Requirement | Spec weight | GitHub MCP | Atlassian Jira MCP | Sentry MCP |
|---|---|---|---|---|
| Authorization Server Metadata (RFC 8414) | MUST | ❌ Not implemented | ✅ | ✅ |
| Dynamic Client Registration (RFC 7591) | SHOULD | ❌ Issue #1081 | ✅ | ✅ |
| PKCE S256 | MUST | N/A (PAT only) | ✅ | ✅ |
| Resource Indicators (RFC 8707) | MUST | N/A | ⚠️ Partial | ✅ |
iss validation (RFC 9207, 2026-07-28 RC) | MUST (upcoming) | N/A | ❌ | ❌ |
Sources: FastMCP #3085 (retrieved 2026-06-05); Stacklok MCP primer (retrieved 2026-06-05); Titanapps Jira MCP guide (retrieved 2026-06-05).
The pattern is consistent: servers that came to market fast skip DCR because no identity provider natively speaks it. WorkOS AuthKit and Auth0 both added DCR support in mid-2025. Okta's DCR is via a non-standard extension. If you are not using one of those three, you are implementing it yourself — which is where most teams stop and fall back to PATs.
The three checks before you ship
- DCR responds correctly to a blank POST.
curl -X POST https://your-mcp-server.example.com/register -H "Content-Type: application/json" -d '{}'should return a201withclient_id,client_secret, andredirect_uris. Returning404or501means Claude.ai's connector will silently fail after OAuth completes.
- Token includes `aud` and `iss` claims. Decode your bearer token (even an opaque token has claims if you use JWT). The
audmust be your MCP server's URL, not GitHub's. The 2026-07-28 RC makes client-sideissvalidation mandatory — your token must carry it for clients implementing the new spec.
- The Authorization Server Metadata document is served without authentication. Teams behind Cloudflare WAF sometimes block unauthenticated
GETrequests to/.well-known/*. The MCP client fetches this before it has any token — blocking it breaks discovery silently.
> KnowledgeCheck — What single missing endpoint prevents GitHub's own remote MCP server from connecting to Claude.ai's connector flow? > > Answer: The `/register` Dynamic Client Registration endpoint (RFC 7591). Claude.ai auto-registers itself as an OAuth client on first connection; without this endpoint the connector flow cannot complete, and the connection shows "Authorization failed" despite a successful GitHub OAuth dance.
The full implementation pattern — including the Cloudflare Workers OAuthProvider wiring, KV token storage, scope mapping from GitHub permissions to MCP tool access, and the /.well-known document template — is covered step by step in courses · claude-mcp-mastery. The course uses the GitHub connector as the running production example throughout Module 4.
See also: blogs · mcp-stranger-to-production-2026 for the end-to-end deployment picture, blogs · 2026-05-13-mcp-server-registry-security for the supply-chain risks introduced by the 91.5% that skip OAuth entirely, and blogs · mcp-2026-roadmap-explained for what the 2026-07-28 spec RC changes for server builders.