-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat: add OAuth example for providers without DCR support #1695
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
travisbreaks
wants to merge
1
commit into
modelcontextprotocol:main
from
travisbreaks:feat/oauth-no-dcr-example
+750
−0
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| # OAuth Without Dynamic Client Registration (DCR) Example | ||
|
|
||
| Many OAuth providers (GitHub, Google, Azure AD, etc.) do not support [RFC 7591 Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591). This example demonstrates how to build an MCP server that authenticates users through such a provider using a **proxy | ||
| pattern**. | ||
|
|
||
| ## The problem | ||
|
|
||
| The MCP specification's OAuth flow expects an Authorization Server that supports DCR. When connecting to a provider that only accepts pre-registered OAuth apps, MCP clients cannot complete the standard registration step. | ||
|
|
||
| ## The solution: OAuth proxy | ||
|
|
||
| The MCP server runs a lightweight OAuth Authorization Server that sits between the MCP client and the upstream provider: | ||
|
|
||
| ``` | ||
| MCP Client <--> OAuth Proxy (this server) <--> Upstream Provider (GitHub) | ||
| - Accepts DCR from clients - No DCR support | ||
| - Proxies auth to upstream - Pre-registered OAuth app | ||
| - Issues its own tokens - Issues upstream tokens | ||
| ``` | ||
|
|
||
| The proxy: | ||
|
|
||
| 1. **Accepts DCR** from MCP clients (issuing proxy-level client credentials) | ||
| 2. **Redirects authorization** to the upstream provider using pre-registered credentials | ||
| 3. **Exchanges upstream tokens** and issues its own tokens to MCP clients | ||
| 4. **Maps proxy tokens to upstream tokens** so the MCP server can call upstream APIs on behalf of the user | ||
|
|
||
| ## Setup | ||
|
|
||
| ### 1. Register an OAuth app with the upstream provider | ||
|
|
||
| For GitHub: | ||
|
|
||
| 1. Go to [GitHub Developer Settings](https://github.com/settings/developers) | ||
| 2. Click "New OAuth App" | ||
| 3. Set the **Authorization callback URL** to `http://localhost:3001/callback` | ||
| 4. Note the **Client ID** and generate a **Client Secret** | ||
|
|
||
| ### 2. Set environment variables | ||
|
|
||
| ```bash | ||
| export OAUTH_CLIENT_ID="your-github-client-id" | ||
| export OAUTH_CLIENT_SECRET="your-github-client-secret" | ||
| ``` | ||
|
|
||
| ### 3. Run the server | ||
|
|
||
| From the SDK root: | ||
|
|
||
| ```bash | ||
| pnpm --filter @modelcontextprotocol/examples-server exec tsx src/oauthWithoutDCR.ts | ||
| ``` | ||
|
|
||
| Or from within this package: | ||
|
|
||
|
Check warning on line 55 in examples/server/src/README-oauthWithoutDcr.md
|
||
| ```bash | ||
| pnpm tsx src/oauthWithoutDCR.ts | ||
| ``` | ||
|
|
||
| ### 4. Connect a client | ||
|
|
||
| Use the example OAuth client: | ||
|
|
||
| ```bash | ||
| pnpm --filter @modelcontextprotocol/examples-client exec tsx src/simpleOAuthClient.ts | ||
| ``` | ||
|
|
||
| ## Configuration | ||
|
|
||
| | Variable | Default | Description | | ||
| | --------------------- | --------------------------------------------- | ------------------------------------ | | ||
| | `OAUTH_CLIENT_ID` | (required) | Client ID from upstream provider | | ||
| | `OAUTH_CLIENT_SECRET` | (required) | Client secret from upstream provider | | ||
| | `MCP_PORT` | `3000` | Port for the MCP server | | ||
| | `PROXY_PORT` | `3001` | Port for the OAuth proxy server | | ||
| | `OAUTH_AUTHORIZE_URL` | `https://github.com/login/oauth/authorize` | Upstream authorization endpoint | | ||
| | `OAUTH_TOKEN_URL` | `https://github.com/login/oauth/access_token` | Upstream token endpoint | | ||
| | `OAUTH_SCOPES` | `read:user user:email` | Space-separated scopes for upstream | | ||
|
|
||
| ## Adapting to other providers | ||
|
|
||
| To use Google instead of GitHub, set: | ||
|
|
||
| ```bash | ||
| export OAUTH_AUTHORIZE_URL="https://accounts.google.com/o/oauth2/v2/auth" | ||
| export OAUTH_TOKEN_URL="https://oauth2.googleapis.com/token" | ||
| export OAUTH_SCOPES="openid email profile" | ||
| ``` | ||
|
|
||
| The proxy pattern works with any standard OAuth 2.0 provider. The only requirement is that you pre-register an OAuth application and provide the credentials via environment variables. | ||
|
|
||
| ## Security considerations | ||
|
|
||
| This example is for **demonstration purposes**. For production use: | ||
|
|
||
| - Use HTTPS for all endpoints | ||
| - Persist client registrations and tokens in a database (not in-memory) | ||
| - Implement proper PKCE validation end-to-end | ||
| - Implement token refresh and revocation | ||
| - Restrict CORS origins | ||
| - Add rate limiting to the registration and token endpoints | ||
| - Validate redirect URIs strictly (exact match, no open redirects) | ||
| - Set appropriate token expiration times | ||
| - Consider adding CSRF protection to the authorization flow | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 The README run commands on lines 51 and 57 reference
oauthWithoutDCR.ts(uppercase DCR), but the actual file isoauthWithoutDcr.ts(lowercase). On case-sensitive filesystems (Linux), these commands will fail with file-not-found. The same typo appears in the .ts file's own usage comment (line 37).Extended reasoning...
Bug: Filename case mismatch in run commands
The file added by this PR is named
oauthWithoutDcr.ts(lowercasedcr), as confirmed by the diff header and the filesystem. However, the accompanying README (README-oauthWithoutDcr.md) references the file asoauthWithoutDCR.ts(uppercaseDCR) in two run commands:pnpm --filter @modelcontextprotocol/examples-server exec tsx src/oauthWithoutDCR.tspnpm tsx src/oauthWithoutDCR.tsAdditionally, the TypeScript file itself contains the wrong case in its own usage docstring comment at line 37:
OAUTH_CLIENT_ID=xxx OAUTH_CLIENT_SECRET=yyy tsx src/oauthWithoutDCR.ts.Why this matters
On case-sensitive filesystems (Linux, which is the standard deployment target and CI environment),
oauthWithoutDCR.tsandoauthWithoutDcr.tsare different filenames. Running the documented commands will produce aCannot find module/ file-not-found error. This would only work silently on case-insensitive filesystems like macOS (default) or Windows.Step-by-step proof
pnpm tsx src/oauthWithoutDCR.tssrc/oauthWithoutDCR.tson the filesystemsrc/oauthWithoutDcr.ts— these are different paths on LinuxNote: the parent
examples/server/README.mdcorrectly referencessrc/oauthWithoutDcr.ts(lowercase), so only the dedicated README and the .ts file's own comment are affected.Fix
Replace
oauthWithoutDCR.tswithoauthWithoutDcr.tson lines 51 and 57 of the README, and on line 37 of the .ts file.