-
Notifications
You must be signed in to change notification settings - Fork 187
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
RFC: Authorization support #133
Conversation
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.
Could we please split "new draft spec version" out from the auth additions? This is pretty difficult to review as there's a bunch of other stuff mixed in. (It's also possible we might want to merge other stuff into the draft spec before our auth discussions are resolved.)
Totally. My bad, sorry. Fixed and set the base branch. |
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.
Some initial thoughts—most are probably bigger discussions, though
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.
Overall: I generally think this is the right direction.
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.
Definitely liking how this becomes more intuitive as we leverage HTTP standards more, but I think we could go a bit further in leveraging OAuth standards.
(discussion in comment)
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.
I think there's two distinct use cases that need to be teased apart. If we can get clarity around that, then I think it'll help align more with OAuth, and result in a more succinct and flexible protocol. The two use cases are:
- Authenticating/authorizing the Client to to the MCP Server ( C <-> M )
- Authenticating/authorizing the MCP Server to other APIs/resource servers.
From what I understand, the current proposal seems to be indexing on the second use case. At the same time, it is also attempting to accomodate 1. However, that's creating a coupling that is not in-line with OAuth. For instance, the the requirement that MCP servers expose OAuth endpoints and the notion of a session token not present in OAuth architectures.
I'd recommend that MCP servers be modeled (from an OAuth perspective) as a resource server, and MCP clients are clients. In scenarios where the the MCP server itself is an OAuth client of a further upstream server, that should be treated as a completely separate OAuth interaction (and one the MCP client is unaware of).
Let's first take a look at the simpler case where the MCP server has no upstream dependencies (ie, it is a "first class" API in its own right), and simply needs to authenticate the user accessing the API. A sequence diagram (with detailed comments) to illustrate:
participant C as Client
participant M as MCP Server
participant A as OAuth Server
1. C->>M: Any MCP Request
2. M->>C: HTTP 401 Unauthorized
3. C->>A: OAuth Authorization Request: GET /authorize?redirect_uri=http://localhost:1234/callback&...
Note over A: Validate redirect_uri, authenticate user, obtain consent, etc.
4. A->>C: Authorization Code (via redirect)
5. C->>A: Exchange Code for Tokens
6. A->>C: Access Token + Refresh Token
7. C->>M: Subsequent requests with Access Token
8. C->>M: Repeat 7 until Access Token invalid, then refresh token or goto 3.
Note that in this modified sequence diagram, the OAuth requests go directly to the OAuth server, rather than to the MCP server as proposed. The session token is then replaced with standard OAuth access tokens and refresh tokens (the later of which represents a logical "session" in this scenario). The MCP client could continue to access the MCP server as long as the access token is valid, or as long as the refresh token is valid and can obtain fresh access tokens. Once the refresh token is invalidated, the MCP client must go through the authorization request again.
Now let's take a look at a scenario where the MCP Server is itself an OAuth client of an upstream API/resource server, and needs its own access tokens and refresh tokens for that API.
participant C as Client
participant M as MCP Server
participant A as OAuth Server
participant R as upstream API/Resource Server
participant A2 as upstream OAuth Server
1. C->>M: Any MCP Request
2. M->>C: HTTP 401 Unauthorized
3. C->>A: OAuth Authorization Request: GET as.example.com/authorize?redirect_uri=http://localhost:1234/callback&...
Note over A: Validate redirect_uri, authenticate user, obtain consent, etc.
4. A->>A2: OAuth Authorization request: GET as.example2.com/authorize?redirect_uri=https://as.example.com/callback&...
Note over A: A needs to "connect" with the upstream API R and get user authorization to access it
Note over A2: Validate redirect_uri, authenticate user, obtain consent, etc.
5. A2->>A: Authorization Code (via redirect)
6. A->>A2: Exchange Code for Tokens
7. A2->>A: Access Token + Refresh Token
Note over A: Access Token and Refresh Token are persisted for use by MCP server M, which is in the "same domain" as A
8. A->>C: Authorization Code (via redirect)
9. C->>A: Exchange Code for Tokens
10. A->>C: Access Token + Refresh Token
11. C->>M: Subsequent requests with Access Token
12. M->>R: API request with Access Token from (7), optionally refreshed with Refresh Token from (7)
13. C->>M: Repeat 7 until Access Token invalid, the refresh token or goto 3.
Note here that there are two completely independent OAuth requests. One from the MCP Client to the OAuth Server A. And another from the OAuth Server A to upstream OAuth Server A2. The tokens issued in each are completely independent. (Backend bookkeeping would associate them with the authenticated user, however).
In this archecture, its assumed that OAuth Server A and MCP Server M are in the "same domain". It's the responsibility of the OAuth Server A to interact with the user and obtain any necessary authorizations for upstream API R (in a "connect with R" style OAuth flow). MCP Server M then uses the resulting access tokens (made available
via some backend storage mechanism) to call upstream API R.
Even though they are in the same domain, there's no coupling - in the sense that the endpoints for authorization are not part of the MCP server. The same HTTP server/process could be hosting both logical entities, but that is a deployment consideration, rather than a protocol requirement.
I'm going to pause my review here, because this proposal might take some time to digest. I do think it models OAuth in a more natural way and preserves flexibility for various usages in so doing. Questions, comments, concerns are all welcome.
I had a similar (less articulate) comment above, since it means there's only a single session token to manage and the server doesn't need additional state. However, I believe the key requirement informing the current proposal is about the authorization request step and the MCP server being the holder of client id and secret for the external service. I believe in this proposed variation where the client does the authorize or token exchange, its not valid for the server to hand those over. am I understanding right? |
Yes - and that should be a requirement for any proposal. If the MCP server is integrating with any upstream APIs or service (postgres, etc), the credentials it uses to access those services must be kept secret and are considered private to the MCP server. It should never be handing out those credentials to clients. |
M->>A: Exchange Code for Tokens | ||
A->>M: Access Token + Refresh Token | ||
M->>C: Redirect to validated redirect_uri with session token | ||
C->>M: Subsequent requests with session token |
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.
Possibly something to consider here, depending on whether keeping the SSE transport browser-compatible is a goal: EventSource
doesn't support setting additional headers, at least in the browser. (It does support sending Cookie
headers automatically using the { withCredentials }
EventSourceInit
option, however.)
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.
This is a really good point. 😕
I don't love cookies as a replacement here. Possibly the most correct answer is, "just use WebSocket," but of course that has its own problems…
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.
Maybe the use of a package like eventsource is an acceptable workaround.
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.
Used eventsource
in this PR: modelcontextprotocol/typescript-sdk#134
To clarify, my question is how that addressed in your first scenario? My read is that is showing the client talking directly to the oath provider that the server is integrated with, and I believe some of those steps in the protocol require client id and secret so not sure how that meets the criteria above. |
Good question. This will depend on the nature of the MCP client. A couple of common scenarios:
OAuth has a lot of optionality here (for better or worse), and I think its worth preserving that optionality so that MCP/OAuth servers can choose their appropriate trust model. For "open" servers, that are broadly accepting of any client connecting, there could be a convention of just using a well-known client ID of In any case, the principle should still hold that the MCP client <-> MCP server OAuth flow is distinct from the MCP server <-> upstream API OAuth flow (if any), and any tokens/client IDs/secrets, etc should remain contained within the respective OAuth flows. |
C->>M: Any MCP Request | ||
M->>C: HTTP 401 Unauthorized | ||
C->>M: GET /authorize?redirect_uri=http://localhost:1234/callback | ||
Note over M: Validate redirect_uri |
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.
To double-check my understanding: does the client provide the redirect_uri
? (I.e., a local client might host a temporary server available at localhost:NNNN
to receive the callback, whereas a client running on a HTTP server might provide https://my-mcp-host.com/
?)
I ask because we do (something similar to) a OAuth2.0 device flow on mcp.run that might sidestep the need to have the callback land back at a local server. (The MCP server requests a code/authorization url from mcp.run, then polls for the code to become valid while presenting the authorization url to the user.) I'm not sure if it's in-scope for this proposal, though!
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.
Yes, exactly. Often times "public" desktop apps embed a web server, bind to localhost, and use the resulting port for their callback URL. Server-side apps would just use their domain and standard port.
One benefit of decoupling the MCP Server and OAuth server, is that authorization flows (and introducing new ones, such as the device flow) don't impact the MCP server itself. That functionality is delegated to the OAuth server, which can be extended as necessary to support new forms of authentication, new flows, etc - without impacting the MCP server itself (just as is the case with typical HTTP/REST APIs).
I suspect the reason to show the redirect-based authorization code flow is simply because they are more common. We should avoid making assumptions that limit interactions to just that flow, however.
Thanks for elaborating. I hear your point that this is a deployment consideration -- though unlikely to meet the design goals for arbitrary 3P auth services, I see the point that it can be supported by the spec (e.g. if the client knows up front that the MCP server and 3rd party meet certain criteria)
Yes, agreed, this sounds right to me and seems very desirable. I can see how it works for more expansive cases and can be simplified for nearer term deployment scenarios. |
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.
Please remove the transport upgrade spec (to be discussed separately), and then I think this is ready to go. 🙏
Thanks for all the hard work putting this together!
This adds a comprehensive authentication specification for the Model Context Protocol that uses OAuth 2.1 for HTTP+SSE transport. The spec defines the complete authentication flow including metadata discovery, dynamic client registration, token handling, and security best practices. It supports both localhost and non-localhost scenarios with appropriate security measures.
BTW, shouldn't we set DRAFT: 202X-XX-XX as Milestone for this PR? |
add Stripe to Official integrations list
Just wanted to know if oauth token exchange flow was ever discussed? https://oauth.net/2/token-exchange/. i.e. This is an example implementation in Azure EntraID You can consider an MCP server as a middle tier resource server, exchanging the incoming token for another token to access downstream services. This would allow an MCP server wrapping an HTTP API to consume the API on behalf of the user. It also allows for easy interoperability with current authn/authz designs as well. Encouraging this design would also decrease the blast radius of "LLM Tool use" for enumeration based attacks as the calls from the MCP server to downstream services will have a "user context" attached which would allow the downstream service to sandbox the operations to that attached "user context". |
Just want to clarify if this is basically an application-level authn/authz or kind of in-the-middle-mcp-server-level-auth? |
Summary
This RFC introduces authentication to the Model Context Protocol, defining how clients and servers can establish secure, authenticated connections while maintaining the protocol's simplicity and flexibility.
The proposal adopts OAuth 2.0 as the standard authentication mechanism, providing a well-understood and battle-tested foundation. Authentication happens at the transport level, keeping the core protocol messaging clean and focused. While authentication requires HTTP transport, the specification includes a clean upgrade path from other transports like STDIO.
Key aspects of the authentication design:
This addition to the specification fills an important gap in the protocol, enabling secure client-server communication while staying true to MCP's principles of simplicity and standards-based design.
This builds on the fantastic work by @k6l3 in #101. Thanks for @jspahrsummers @jerome3o-anthropic for endless discussion to get to a very early RFC.
@nick-merrill
@jaredhanson
@allenporter would love to get your input.