-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Add OAuth 2.1 authentication for stdio mode with Docker support #1836
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
base: main
Are you sure you want to change the base?
Conversation
- Create internal/oauth package with standard OAuth implementation - Use golang.org/x/oauth2 for PKCE flow with S256 challenge - Support interactive browser-based authorization - Add CLI flags: --oauth-client-id, --oauth-client-secret, --oauth-scopes - Respect --gh-host for GHES/GHEC OAuth endpoints - Secure implementation with ReadHeaderTimeout, state validation - Comprehensive tests and linting passing Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
- Document OAuth 2.1 PKCE flow for stdio mode - Include setup instructions for GitHub OAuth apps - Document environment variables and CLI flags - Add usage examples with custom scopes - Note that OAuth is stdio-only (not for remote server) Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
- Add DefaultAuthTimeout constant for configurability - Fix string index check to handle idx >= 0 correctly - Use cmd.Context() for proper cancellation support - Add named constant for PKCE verifier length in test - Remove unused context import Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
- Clarify string index boundary check logic (idx > 0 is correct) - Update error message to show both flag and env var options - Add comment explaining the boundary check reasoning Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
- Linting: ✅ 0 issues - Tests: ✅ All passing - CodeQL: ✅ 0 security alerts - Build: ✅ Binary builds successfully Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
|
@copilot this is a great start but the binary is normally distributed through docker, so we need to do more of the pattern in #1677 Although perhaps, if out of band server sent secure URL elicitation outside of a tool call is possible, if opening a browser fails we should try secure url elicitation sent from server after initialize, and if that doesn't work, we could log the url... but wait the callback url won't won't from the host system in docker. So we might have to fall back to device flow if all other options are exhausted... This is tough. Docker massively complicates things. Can't have a random port open. It would need to be predefined and accessible from localhost on the host system, so port would need to be bound explicitly in docker startup command. |
You're absolutely right - Docker makes the callback server approach impossible since the container's localhost isn't accessible from the host's browser. I'll implement a multi-strategy approach:
This way:
Working on this now. |
- Add StartDeviceFlow for environments without callback capabilities - Add StartOAuthFlow that auto-selects between device and interactive flows - Detect Docker environment and use device flow automatically - Add --oauth-callback-port flag for advanced Docker users with port binding - Support fixed ports in callback server for Docker -p usage - Update all OAuth endpoints to include device auth URL - Comprehensive tests for both flows and port configurations - Update README with Docker-specific OAuth instructions Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
|
@copilot the port binding isn't hard because we also distribute the configuration as you can see in the README so discovery of how to do this can be simple and automated, via installation instructions. |
- Add "Quick Setup" section showing simplest paths - Add "Pre-configured MCP Host Setup" with examples - Show Claude and VS Code configuration patterns - Emphasize that port binding is straightforward and automated - Provide ready-to-use config snippets for distribution Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
Absolutely right! I've added a "Pre-configured MCP Host Setup" section with ready-to-use configuration examples for Claude and VS Code. The examples show how port binding can be distributed as simple config snippets, making OAuth setup as straightforward as PAT configuration. The documentation now includes:
See commit |
|
@copilot its normal to provide client secret always either GitHub app, and it only allows user to auth as themselves, so I would always recommend examples and config assume both are present. |
- Remove "optional" language for client secret - Add client secret to all code examples - Update CLI flag description to say "recommended" - Update code comment from "Optional" to "Recommended" - Align with best practice that GitHub OAuth apps should provide both ID and secret Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
|
@copilot modelcontextprotocol/modelcontextprotocol#1036 is how secure url mode elicitation is specified. Is that something you can introduce, and does it support unsolicited server events via stdio because the ideal would be we prompt the user. |
Updated all examples and documentation to include both client ID and secret. Removed "optional" language and updated to recommend providing client secret for all GitHub OAuth app configurations. Changes in commit
This aligns with the best practice that GitHub OAuth apps should provide both credentials. |
|
@copilot to clarify auto opening browser is best, but failing that elicit a url login is best |
Yes, the MCP SDK v1.2.0 we're using already supports URL mode elicitation! This is perfect for the OAuth flow. The implementation would:
This supports unsolicited server events via stdio - the server can send the elicitation request at any time, and later send the completion notification. The client would handle prompting the user to open the URL. However, this requires client support for URL elicitation. Since this is a proposal (SEP), not all MCP clients may support it yet. The current implementation with automatic environment detection (device flow for Docker, PKCE for native) works with all clients today. Should we add URL elicitation as a third option that falls back to the current approaches if the client doesn't declare URL elicitation capability? |
|
@copilot its no longer a proposal, it is part of the spec 2 months, and we cannot do much else given other constraints. User can always provide PAT up front. |
Understood. The priority order would be:
The current implementation handles #1 and #3. We can add #2 as middleware: try auto-open → if fails, send URL elicitation request → if client doesn't support it, fall back to device flow. This would require:
Should I implement this fallback strategy? |
Understood - URL elicitation is now part of the official spec and should be implemented. I'll add it as the fallback between auto-open browser and device flow. Implementation plan:
This gives the best UX while maintaining compatibility with all clients. Working on this now. |
|
@copilot That three step pattern seems almost great to me! But the device flow also requires elicitation. So that's not really a fallback. So I think we probably need to work out if we can use the oauth port for standard flow, and if not do the device flow, and secure url elicitation is requirement for the device flow. So it's not really a 3 part fallback, callback or device flow needs to be decided up front and then elicitation is both a fallback for failing to open Browser for standard flow but also a requirement for device flow in my book. |
You're absolutely right - I was thinking about it wrong. The correct architecture is: Decision point (upfront based on environment):
URL elicitation is not a fallback between flows - it's:
Current implementation has the binary choice (PKCE vs Device) but doesn't use URL elicitation for either yet. Should I implement URL elicitation for both flows? |
|
@copilot that is correct please implement. |
I've identified an architectural constraint: the OAuth flow currently happens before the MCP server starts (we need a token to initialize the server), but URL elicitation requires an active MCP session to send requests to the client. To implement URL elicitation properly, we would need to:
An alternative approach would be to implement a lighter version now:
Which approach would you prefer? Or should I proceed with the full architectural refactor to enable OAuth after server initialization? |
Summary
Implements OAuth 2.1 authentication for stdio mode with automatic environment detection. Uses interactive PKCE flow for native binaries and device flow for Docker containers. Falls back to PAT if OAuth not configured.
Why
stdio users currently must manually create and manage Personal Access Tokens. OAuth provides a better UX with interactive browser consent while maintaining security through PKCE. Docker distribution requires device flow since callback servers don't work in containers.
What changed
internal/oauthpackage implementing OAuth 2.1 with dual flow support:--oauth-client-id,--oauth-client-secret,--oauth-scopes,--oauth-callback-port--gh-hostfor GHES/GHEC supportrepo,user,gist,notifications,read:org,projectMCP impact
Security / limits
golang.org/x/oauth2(no custom auth code)Tool renaming
Lint & tests
./script/lint./script/testDocs
Usage:
Pre-configured for MCP Hosts:
Flow Selection (Automatic):
--oauth-callback-port→ Interactive PKCE flowInteractive PKCE Flow:
Device Flow:
OAuth Best Practice:
OAuth Flow Architecture:
The implementation uses a binary decision based on environment:
URL Elicitation Integration (Future Enhancement):
MCP SDK v1.2.0 supports URL mode elicitation (official spec). This can enhance both flows:
URL elicitation provides native integration with MCP client UI for OAuth prompts. Current implementation works with all clients by using browser auto-open (PKCE) and stderr output (device flow). Adding URL elicitation would improve UX when MCP clients support it.
stdio-only by design. Remote server continues using PATs. Port binding setup is straightforward and can be automated through installation instructions and distributed configurations.
Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.