Skip to content
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: Authentication support #133

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open

RFC: Authentication support #133

wants to merge 3 commits into from

Conversation

dsp-ant
Copy link
Member

@dsp-ant dsp-ant commented Jan 8, 2025

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:

  • Builds on existing OAuth 2.0 standards rather than inventing custom schemes
  • Separates authentication concerns from core protocol messaging
  • Provides clear guidance on token handling, security requirements, and error cases
  • Maintains flexibility by making authentication optional but standardized

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.

Copy link
Member

@jspahrsummers jspahrsummers left a 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.)

@dsp-ant dsp-ant changed the base branch from main to davidsp/draft January 8, 2025 21:24
@dsp-ant dsp-ant requested a review from jspahrsummers January 8, 2025 21:24
@dsp-ant
Copy link
Member Author

dsp-ant commented Jan 8, 2025

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.

Copy link
Member

@jspahrsummers jspahrsummers left a 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

docs/specification/draft/basic/authentication.md Outdated Show resolved Hide resolved
Comment on lines +41 to +48
### Authorization Endpoints

MCP servers **MUST** expose the following OAuth-related endpoints:

1. `/authorize` - Initial authorization endpoint
2. `/token` - Token exchange endpoint

These endpoints are relative to the MCP server's base URL. For example, if the MCP server is at `https://example.com/mcp/`, the authorize endpoint would be `https://example.com/mcp/authorize`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you land on this instead of returning the URLs in the 401? This feels a bit more constraining for servers, while making things easier for clients—not convinced that's the right tradeoff, though I don't feel too strongly.

Copy link
Member Author

@dsp-ant dsp-ant Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to stay within HTTP verbs that libraries can easily understand. Providing URLs as part of the body is just something I've never seen and will require custom handling on the client side. The body cannot be introspected by proxies and other standard HTTP infrastructure. I decided I want to stay within the standard HTTP paradigm.

1. Clients **MUST** securely store tokens following OAuth 2.0 best practices
2. Servers **SHOULD** enforce token expiration and rotation
3. All authorization endpoints **MUST** be served over HTTPS
4. Clients **SHOULD** implement refresh token flows for long-lived sessions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
4. Clients **SHOULD** implement refresh token flows for long-lived sessions
4. Servers **SHOULD** implement refresh token flows for long-lived sessions

Clients don't control this, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to think more about this and would love to get input. I think this actualy might be correct but the refresh flow is not declared. The question here is, should session tokens be refreshed? You are right that SERVERS should always refresh since they hold oauth tokens. There is an additional questions if the session token for the client should also be refreshed.

Copy link

@gching gching Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my mind there are two options -

  • Session tokens - Servers would need to handle session management entirely and store the access + refresh and pass over the session token as described in the spec
  • Fully managed by the client - Client receives the access + refresh token and manages the token usage without complex session management on the Server. In this case, the Client would be using the access token instead of a session token.

My opinion is that the second option (fully managed by the client) would be much more simpler and provides more freedom for the spec - it keeps it simple without needing to include more complex session management in the MCP server implementation, which I feel like is a separate concern and gives developers for Clients the freedom to do what they need with the OAuth tokens (ie. implement their own session management)

Copy link

@allenporter allenporter Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultimately, at some point, the client will need to update the auth token that it passes to the MCP server in the request header.

If the client is not doing it's own refreshes, then that implies the server needs to return back the token to the client. I don't think that is right, otherwise why would the client pass anything at all....

I'm wondering if the client actually needs to do /all/ the steps here, and the server not do any of the exchange/refresh. I realize the motivation behind the current proposal since the common case is the server is acting as a client to a third party.

Edit: This comment was made with a misunderstanding of part of the problem statement. Please resolve.

Copy link

@gching gching Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh wait, correct me if I am wrong, but is the motivation for this spec for authentication for the MCP server to an external resource or is it authentication for the MCP client to MCP server 🤔?

Reading this spec more, it seems to be more of the former (MCP Server -> External Resource), since the MCP server would be managing the refresh token

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To the specific question(s) above: my understanding is that session tokens don't refresh, and will eventually expire. If that happens, the client should restart the auth flow—at which point, the server could utilize its OAuth refresh token.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, I now have the complete mental model. Not sure if this is appropriate to include in the spec but it could make a nice intro on the problem being solved here or assumptions made.

Not to anchor on home assistant too much here, but i realize it is fairly similar in that regard. I wrote up a quick TL;DR of how it works. This also touches on Nick's comment about using OAuth for the client/server auth too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jspahrsummers - your formulation of the problem statement here is quite helpful and agreed that it would be good to add it to the spec / documentation as an introduction (cc @dsp-ant)

Copy link

@gching gching Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jspahrsummers Makes sense - agreed that clients will not have knowledge so there needs to be some sort of way of retrieving those, either through the MCP server itself or a separate auth service that the MCP server potentially advertises. Given the other discussions below, I'm feeling it should be a separate auth service while keeping the MCP server as a "resource" server according the the OAuth spec.

This also opens up the case where MCP servers that require third-party service resources can either have their own separate auth service or even at a simpler level, advertise the third-party's auth service to retrieve the tokens for the client to make authenticated calls with (sso, social login)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jspahrsummers Makes sense - agreed that clients will not have knowledge so there needs to be some sort of way of retrieving those, either through the MCP server itself or a separate auth service that the MCP server potentially advertises. Given the other discussions below, I'm feeling it should be a separate auth service while keeping the MCP server as a "resource" server according the the OAuth spec.

I am not sure I fully follow. Can you write up a proposal or a diagram?

One thing I want to highlight. If we add proper token exchange and refresh between client and server to this proposal, we get a nice property: An MCP server can just be it's own oauth service OR it can choose to forward to another OAuth service. Both have the same flow if we get this correct and hence it becomes easy to chain as well as simple to the client.

2. Servers **SHOULD** enforce token expiration and rotation
3. All authorization endpoints **MUST** be served over HTTPS
4. Clients **SHOULD** implement refresh token flows for long-lived sessions
5. Servers **MUST** validate redirect URIs to prevent open redirect vulnerabilities
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does a server do this validation?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1
It's not clear what's the mechanism for MCP Clients to register their redirect URIs in the MCP Server.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also share this concern - we talked at one point about using the Origin header, however I'm fuzzy on the details on how this would work. I can imagine some potential stateful ways of solving it but haven't thought it through properly

Comment on lines +29 to +31
C->>M: GET /authorize?redirect_uri=http://localhost:1234/callback
Note over M: Validate redirect_uri
M->>C: Redirect to OAuth Server
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The details about redirection are a bit scant here… does this mean literally HTTP 30x?

And should the GET /authorize be happening in a web browser context?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I'l add more comments. GET /authorize happens either in a browser or outside of a browser, up to client. The redirected URl needs to happen in the browser. I think clients will do /authorize in the browser already and rely on redirect mechanics.

"protocolVersion": "2024-11-05",
"upgrade": {
"endpoint": "http://localhost:8080",
"transport": "http+sse"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to standardize a list of transport strings if we pursue this.

@@ -102,6 +105,36 @@ The server **MUST** respond with its own capabilities and information:
}
```

#### Upgrading Transports
The server **MAY** request a transport change by including an `upgrade` field in the initialize response:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts about representing this as an error instead? It's a bit out of the normal initialization flow, and missing some fields that we normally require (e.g., capabilities), which will make parsing more annoying.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not an error. Its a legitimate correct respond. We can say that the respond of initialize is EITHER an upgrade or the standard initialize response, e.g. InitializeResponse = UpgradeResponse | ServerInitializeResponse

Comment on lines +129 to +132
When receiving an upgrade response, the client **MUST**:
1. Close the current transport connection
2. Connect to the specified endpoint using the new transport
3. Perform initialization again on the new transport
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clients should be allowed to reject transports they don't support, somehow.

Comment on lines +45 to +46
1. `/authorize` - Initial authorization endpoint
2. `/token` - Token exchange endpoint
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this more - maybe it wasn't clear from #101 and the comments, but whats the rationale between implementing these endpoints directly on the MCP server versus advertising it (#101 (comment))? Is it because it would keep the spec simpler for Clients to implement with

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would be easier. The only, standard compliant way to advertise would be through well-known OpenID/OAuth discovery, which significantly blows up the scope. Every other mechanism would be unusual and non-standard.

1. `/authorize` - Initial authorization endpoint
2. `/token` - Token exchange endpoint

These endpoints are relative to the MCP server's base URL. For example, if the MCP server is at `https://example.com/mcp/`, the authorize endpoint would be `https://example.com/mcp/authorize`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is written here seems fine, but i am not aware that there is already a strict definition of an MCP server url structure. I don't see URL anywhere mentioned in the spec website. Is this actually defined yet?

As an example when implementing a client using the SSE client library, my impression was I would need to give a base url to hand out was directly the endpoint e.g. https://example.com/some-path/see and everything in relation is undefined (e.g. the post endpoint is only discovered based on the response, or by convention, but not actually part of the spec)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're correct about the current state. It may be the case that we want to make this stricter/more standardized going forward, though.

1. Clients **MUST** securely store tokens following OAuth 2.0 best practices
2. Servers **SHOULD** enforce token expiration and rotation
3. All authorization endpoints **MUST** be served over HTTPS
4. Clients **SHOULD** implement refresh token flows for long-lived sessions
Copy link

@allenporter allenporter Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultimately, at some point, the client will need to update the auth token that it passes to the MCP server in the request header.

If the client is not doing it's own refreshes, then that implies the server needs to return back the token to the client. I don't think that is right, otherwise why would the client pass anything at all....

I'm wondering if the client actually needs to do /all/ the steps here, and the server not do any of the exchange/refresh. I realize the motivation behind the current proposal since the common case is the server is acting as a client to a third party.

Edit: This comment was made with a misunderstanding of part of the problem statement. Please resolve.

Copy link

@allenporter allenporter left a 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.

Base automatically changed from davidsp/draft to main January 9, 2025 11:31

## Security Considerations

1. Clients **MUST** securely store tokens following OAuth 2.0 best practices
Copy link

@siacomuzzi siacomuzzi Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be Servers instead of Clients, right?

  • OAuth 2.0 tokens (access token + refresh token) are stored by the MCP Server.
  • MCP Clients store the session token.

MCP servers **MUST** expose the following OAuth-related endpoints:

1. `/authorize` - Initial authorization endpoint
2. `/token` - Token exchange endpoint

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where would the <mcp_server>/token endpoint be used?

Copy link

@nick-merrill nick-merrill left a 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)

docs/specification/draft/basic/authentication.md Outdated Show resolved Hide resolved

These endpoints are relative to the MCP server's base URL. For example, if the MCP server is at `https://example.com/mcp/`, the authorize endpoint would be `https://example.com/mcp/authorize`.

### Session Token Usage

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.

If I'm understanding correctly, the current implementation requires that the MCP server performs some of the roles that an OAuth server could be responsible for. I'd like to leave room for us to decouple the auth server and the MCP server down the road.

In some cases, I do think MCP servers should store intermediary access and refresh tokens (e.g. when the MCP server owns its own resources, or when the MCP server does not want to give the client a token that can be used directly with some 3rd party API). Even in this case, I think the MCP server should still return some access token and some refresh token, in line with the OAuth standard. This is (A) more secure, (B) more expected, and (C) leaves us the option later to separate the auth flow from the MCP server entirely.

I think we could benefit from removing the concept of "session token" and replace it with "client's OAuth2 access token". I'm thinking that will be more familiar to most developers. This would leave the door open for us to generalize the OAuth flow to allow clients to get their tokens from centralized servers instead of through the MCP server, if the demand for that arises. I think demand for this is quite possible down the road. For example, in a world where a client talks to hundreds of MCP servers, it may become valuable to have a centralized MCP OAuth server.

Maybe we could take a middleground approach:

  1. Keep the limitation that clients must go through the MCP server for OAuth, but
  2. replace the concept of "session token" with standard OAuth (access + refresh) tokens

This will:

  • Align with widely understood OAuth patterns (more familiar to developers)
  • Provide better security through standard token lifecycle management
  • Give us the MCP server the flexibility to either forward OAuth tokens directly to the client or grant its own tokens
  • Leave us the option to allow independent OAuth flows

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your point about reusing OAuth access tokens and refresh tokens down to the client makes sense to me (and would address the other thread wherein we're a bit confused about how/when the client refreshes its token).

FWIW: From my reading, I don't think anything here requires that the MCP server specifically hosts the OAuth flow. The URLs issued could point directly to an external auth server AFAICT.

Copy link

@gching gching Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to leave room for us to decouple the auth server and the MCP server down the road.

FWIW: From my reading, I don't think anything here requires that the MCP server specifically hosts the OAuth flow. The URLs issued could point directly to an external auth server AFAICT.

Agreed, separation of concerns and keeping the MCP server more simpler I feel might be a better approach. #133 (comment) might be a much simpler way if the server just advertises it 🤔

For example, in a world where a client talks to hundreds of MCP servers, it may become valuable to have a centralized MCP OAuth server.

Agreed, that was what I was thinking as well and what I am personally looking into

  1. replace the concept of "session token" with standard OAuth (access + refresh) tokens

Give us the MCP server the flexibility to either forward OAuth tokens directly to the client or grant its own tokens

Loving this as well, keeps it closer to the standard and gives the flexibility / freedom for the MCP server to handle its own session by creating its own OAuth tokens (session-based auth) or pass through OAuth tokens from another provider (SSO, social login)

In the case of a spec, we could mention that an accessToken is always returned and an refreshToken is optionally returned - this will also give the freedom for MCP servers and/or a decoupled authentication server to either pass a singular access token and optionally a way for the client to refresh the token

Copy link

@jaredhanson jaredhanson left a 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:

  1. Authenticating/authorizing the Client to to the MCP Server ( C <-> M )
  2. 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.

@allenporter
Copy link

Note that in this modified sequence diagram, the OAuth requests go directly to the OAuth server, rather than to the MCP server as proposed.

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?

@jaredhanson
Copy link

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

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.)

@allenporter
Copy link

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.

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.

@jaredhanson
Copy link

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:

  • In the case of a desktop/mobile application (think Claude Desktop) that would be what OAuth terms a "public client". The distinction here is that while it may have a client ID, it does not have a client secret (and as such the OAuth server does not authenticate the client). Nonetheless, it can still obtain tokens and is responsible for protecting those.

  • While I don't know of any off the top of my head, I could imagine MCP clients being implemented as backend applications, which would be capable of having a client ID and secret (and keeping it secret).

  • There's more nuanced options than this that are being discussed in various OAuth extensions and proposals, but I'll defer on those for now.

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 client_id=public (or something to that effect). In this case, there'd be no explicit requirement to register a client with the OAuth server, and you might enforce that such clients use localhost domains for callback URLs. (Note: I'd want to cross-reference existing OAuth proposals to see if there's any established conventions for well-known public client ID, in such open access cases. I recall seeing some discussion about this, but I can't find it at the moment).

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

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!

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants