-
Notifications
You must be signed in to change notification settings - Fork 42
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
[proposal] Add auth
as a capability with support for OAuth 2.0 and secrets.
#101
Changes from all commits
c14139b
9b9340d
a5ff8c2
ee1676b
b19ccfe
1a665e3
12f3ab9
c9f37bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
title: Next | ||
weight: 1 | ||
--- | ||
This is the draft version of the specification. Proposals in this revision may not necessarily make it into a release. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
--- | ||
title: "Authentication" | ||
type: docs | ||
weight: 50 | ||
--- | ||
|
||
{{< callout type="warning" >}} | ||
Auth is **experimental**, and being drafted for release in the next [revision]({{< ref "/specification/revisions" >}}) of the protocol. | ||
|
||
The additions to the base protocol are backwards compatible to revision 2024-11-05; however, **the auth specification may change in backwards incompatible ways** until the next protocol revision. | ||
{{< /callout >}} | ||
|
||
The Model Context Protocol (MCP) provides a standardized way for clients and servers to establish secure communication channels. The authentication framework supports multiple authentication methods to accommodate different security requirements and use cases. | ||
|
||
## User Interaction Model | ||
|
||
Authentication in MCP is designed to be flexible and secure. OAuth 2.0 for standardized authorization flows. Other authentication schemes may also be implemented by clients and servers as extensions to the base protocol. | ||
|
||
## Capabilities | ||
|
||
## Server Response | ||
|
||
Servers that support authentication **MUST** include their supported authentication methods in their [initialization]({{< ref "/specification/basic/lifecycle#initialization" >}}) response: | ||
|
||
```json | ||
{ | ||
"capabilities": { | ||
"auth": { | ||
"oauth2": { | ||
"authorize": {}, | ||
"token": {}, | ||
"revoke": {} | ||
}, | ||
} | ||
} | ||
} | ||
``` | ||
|
||
Servers that support non-standard authentication schemes can declare them as experimental capabilities, with optional parameters: | ||
|
||
```json | ||
{ | ||
"capabilities": { | ||
"experimental": { | ||
"auth": { | ||
"mycustomauth": {} | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
## Security Considerations | ||
|
||
1. Clients **MUST**: | ||
- Store and manage authentication tokens and credentials securely | ||
- Implement a flow to allow users to revoke their token | ||
|
||
2. Servers **MUST**: | ||
- Implement proper security measures for storing and managing secrets | ||
|
||
## Implementation Guidelines | ||
|
||
1. Clients **SHOULD**: | ||
- Prompt users for consent before initiating authentication flows | ||
- Provide clear user interfaces for authentication management | ||
- Handle authentication errors gracefully | ||
|
||
2. Servers **SHOULD**: | ||
- Provide clear documentation for authentication requirements | ||
- Provide clear error messages for authentication failures where applicable |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
--- | ||
title: OAuth 2.0 Authentication | ||
type: docs | ||
weight: 51 | ||
--- | ||
|
||
{{< callout type="warning" >}} | ||
Auth is **experimental**, and being drafted for release in the next [revision]({{< ref "/specification/revisions" >}}) of the protocol. | ||
|
||
The additions to the base protocol are backwards compatible to revision 2024-11-05; however, **the auth specification may change in backwards incompatible ways** until the next protocol revision. | ||
{{< /callout >}} | ||
|
||
The Model Context Protocol (MCP) supports [OAuth 2.0](https://oauth.net/2/) as a standardized authentication method, allowing secure authorization flows between clients and servers. Tokens will be securely communicated as part of the request body. | ||
|
||
## Protocol Flow | ||
We follow the flows defined by [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749). | ||
|
||
```mermaid | ||
sequenceDiagram | ||
participant Client as MCP Client | ||
participant User as Resource Owner (End User) | ||
participant AuthServer as Authorization<br> MCP Server | ||
participant Resource as Resource<br>MCP Server | ||
|
||
Note right of Client: Obtaining an access token: | ||
Client->>User: Authorization Request (auth/oauth2/authorize) | ||
User->>Client: Authorization Grant | ||
Client->>AuthServer: Authorization Grant (auth/oauth2/token) | ||
AuthServer->>Client: Access Token | ||
|
||
Note right of Client: Using an access token: | ||
Client->>Resource: Access prompts/resources/tools with Access Token | ||
Resource->>Client: Receive protected response | ||
|
||
|
||
Note right of Client: Refreshing an access token: | ||
Client->>AuthServer: Refresh Token Request (auth/oauth2/token) | ||
AuthServer->>Client: New Access Token | ||
|
||
Note right of Client: Revoking tokens: | ||
Client->>AuthServer: Revoke Token Request (auth/oauth2/revoke) | ||
``` | ||
Note that the authorization server may be the same server as the resource server or a separate entity. A single authorization server may issue access tokens accepted by multiple resource servers. | ||
|
||
## Capabilities | ||
|
||
|
||
Servers supporting OAuth 2.0 **MUST** include their capabilities: | ||
|
||
```json | ||
{ | ||
"capabilities": { | ||
"auth": { | ||
"oauth2": { | ||
"authorize": {}, | ||
"token": {}, | ||
"revoke": {} | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
## Flows | ||
### Initialization | ||
During initialization, if the server supports the `oauth2` capability, the client **SHOULD** include an access token in all subsequent requests. If the client does not have an access token, the client **SHOULD** obtain one. | ||
|
||
### Obtaining an Access Token | ||
|
||
#### Authorization Grant Flow | ||
To obtain an access token, clients **MUST** send an `auth/oauth2/authorize` request. | ||
|
||
**Request:** | ||
```typescript | ||
{ | ||
"jsonrpc": "2.0", | ||
"id": 1, | ||
"method": "auth/oauth2/authorize", | ||
"params": { | ||
"response_type": "code", // REQUIRED | ||
"client_id": "client_id", // REQUIRED | ||
"redirect_uri": "redirect_uri", // OPTIONAL | ||
"scope": "scope", // OPTIONAL | ||
"state": "state", // RECOMMENDED | ||
} | ||
} | ||
``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious why the authorization request is being sent "in-band" on the JSON-RPC channel, especially given that the response is an I'd suggest that it would be more in-line with OAuth 2.0's model to advertise the endpoints in the capabilities: {
"capabilities": {
"auth": {
"oauth2": {
"authorize": { "url: "https://mcp.example.com/authorize" },
"token": { "url: "https://mcp.example.com/token" },
"revoke": { "url: "https://mcp.example.com/revoke" }
}
}
}
} Given these capabilities, the client would request authorization by redirecting directly to the /authorize URL in a browser with the necessary params:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optionally, you may specify only the Authorization Server Metadata url (defined by RFC 8414): {
"capabilities": {
"auth": {
"oauth2": {
"metadata": { "url: "https://mcp.example.com/.well-known/oauth-authorization-server" }
}
}
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. This makes sense to me, as it's valuable to support Auth Servers that are not the same as the Resource Servers. To utilize the learnings from RFC 8414 but avoid an additional roundtrip, maybe we could use the RFC 8414 shape? {
"capabilities": {
"auth": {
"oauth2": {
"metadata": {
"issuer": "https://server.example.com",
"authorization_endpoint": "https://server.example.com/authorize",
"token_endpoint": "https://server.example.com/token",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code"],
"token_endpoint_auth_methods_supported": ["client_secret_basic"]
}
}
}
}
} |
||
**Response:** | ||
```json | ||
{ | ||
"jsonrpc": "2.0", | ||
"id": 1, | ||
"result": { | ||
"authorization_url": "<scheme>://<domain>/oauth2" | ||
} | ||
} | ||
``` | ||
- The client **MUST** redirect the user to the authorization URL. | ||
- The authorization URL **SHOULD** prompt the user to configure any credentials as needed. Once complete, the server provides an authorization grant in the form of a code. | ||
- The client **MUST** implement a way to receive the authorization grant. This can be through a callback such as the redirect URI, providing some interface for the user to provide it, etc. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the above suggested change to redirect directly to /authorize, it is still the client's responsibility to receive the authorization grant (ie, the authorization code). Now, however, there's no need for the intermediary JSON-RPC request. |
||
#### Access Token Exchange Flow | ||
|
||
After receiving the authorization code, the client **SHOULD** exchange it for tokens: | ||
|
||
```json | ||
{ | ||
"jsonrpc": "2.0", | ||
"id": 1, | ||
"method": "auth/oauth2/token", | ||
"params": { | ||
"grant_type": "authorization_code", | ||
"code": "code", | ||
"state": "state" | ||
} | ||
} | ||
``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd similarly suggest that the client could make a call directly to the advertised /token endpoint, rather using a JSON-RPC method. Note that this isn't avoiding some unnecessary intermediate request (as with the suggestion for /authorize), since there's no redirect. Its a single request either way, either via HTTP or JSON-RPC. Using the HTTP request directly allows the use of existing deployed OAuth 2.0 authorization servers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. If we support OAuth2 fully, then that would mean tying ourselves to HTTP. That's fine (and probably desirable) when using any HTTP transport (like SSE) but problematic when using a non-HTTP transport like stdio. However, we could just note that as a limitation of the stdio transport. stdio and OAuth2 would still work pretty well as long as the Auth Server is hosted independently. For stdio, I think we would still want to use the |
||
|
||
The server responds with tokens: | ||
|
||
```typescript | ||
{ | ||
"jsonrpc": "2.0", | ||
"id": 2, | ||
"result": { | ||
"access_token": "access_token", // REQUIRED | ||
"token_type": "token_type", // REQUIRED | ||
"expires_in": 3600, // RECOMMENDED | ||
"scope": "scope", // OPTIONAL if idential to client-requested scope, otherwise REQUIRED | ||
"state": "state" // REQUIRED if the "state" parameter was present in the client authorization request. | ||
// OPTIONAL additional parameters | ||
} | ||
} | ||
``` | ||
- The client **MUST** securely store the access token, and a refresh token if one is included. | ||
- The server **MUST** implement rate-limiting to prevent abuse. | ||
|
||
### Utilizing an Access Token | ||
|
||
Once a client has obtained an access token, it **SHOULD** include it in all requests in the parameters, including the initalization request. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would there be consideration for supporting standards around Making Authenticated Requests with OAuth to make this part of the transport layer? I noticed that here in the PR and in the discussion thread there has not been mention/exploration of using authentication built into the transport layer. I can see the rationale for the current proposal given:
As a result, the implication here is that the MCP server effectively must act like an OAuth client for some third party service, even if that third party is themselves. An existing service adding support for MCP you can't use it's existing authentication stack provided at the transport layer. Instead, all MCP requests are unauthenticated at the HTTP level, and invisible to the web app framework. To give a concrete example: We have implemented an MCP server in Home Assistant and reusing the existing authentication stack, already built, would go a long way. I suspect this would help with MCP server adoption for well established systems. I don't have an answer for how to make this easier to support for stdio, but IMO the complexity should be pushed to stdio servers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I tend to think we should lean into the OAuth2 HTTP-based standard (using the MCP client SDKs can help make it easier to reason about how OAuth is handled for other transports. |
||
|
||
**Request:** | ||
|
||
```json | ||
{ | ||
"jsonrpc": "2.0", | ||
"id": 1, | ||
"method": "...", | ||
"params": { | ||
"_meta": { | ||
"auth": { | ||
"oauth2": { | ||
"access_token": "..." | ||
} | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
### Handling Expired Tokens | ||
|
||
If the client has a refresh token, the client **SHOULD** automatically request a new access token with a `auth/oauth2/token` request. If the client does not have a valid refresh token, it **SHOULD** obtain an access token as if the user was authenticating for the first time. | ||
|
||
**Request:** | ||
|
||
```typescript | ||
{ | ||
"jsonrpc": "2.0", | ||
"id": 1, | ||
"method": "auth/oauth2/token", | ||
"params": { | ||
"refresh_token": "refresh_token", // REQUIRED | ||
"grant_type": "refresh_token", // REQUIRED, must be set to `refresh_token` | ||
"scope": "scope" // OPTIONAL | ||
} | ||
} | ||
``` | ||
The response will be identical to obtaining an access token for the first time. | ||
|
||
### Revoke Token Flow | ||
|
||
**Request:** | ||
|
||
```json | ||
{ | ||
"jsonrpc": "2.0", | ||
"id": 1, | ||
"method": "auth/oauth2/revoke", | ||
"params": { | ||
"token": "access_or_refresh_token" | ||
} | ||
} | ||
``` | ||
|
||
## Error Handling | ||
### Error Responses | ||
If a request failed client authentication or is invalid the server should respond with an error response as described in [Section 5.2 of RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749#section-5.2). | ||
|
||
**Response:** | ||
```typescript | ||
{ | ||
"jsonrpc": "2.0", | ||
"id": 1, | ||
"error": { | ||
"code": -32001, | ||
"message": "Auth error, please see nested data.", | ||
"data": { | ||
"authRequest": { | ||
"oauth2": { | ||
"error": "ASCII error code from 5.2", // REQUIRED | ||
"error_description": "Helpful message", // OPTIONAL | ||
"error_uri": "Helpful webpage" // OPTIONAL | ||
} | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
Clients **SHOULD** handle errors as gracefully as possible with automated token refresh logic and presenting errors clearly. | ||
|
||
|
||
### Server Guidelines | ||
|
||
## Security Considerations | ||
|
||
1. Clients **MUST**: | ||
- Follow security best practices outlined in the [OAuth 2.0 framework](https://datatracker.ietf.org/doc/html/rfc6749) | ||
- Securely store tokens | ||
- Provide clear user interfaces to token management, including obtaining and revoking tokens | ||
|
||
2. Servers **MUST**: | ||
- Follow security best practices outlined in the [OAuth 2.0 framework](https://datatracker.ietf.org/doc/html/rfc6749) | ||
- Implement rate limiting for token endpoints | ||
- Validate all tokens | ||
- Support token revocation | ||
- Maintain secure storage of all secrets | ||
|
||
3. Servers **SHOULD**: | ||
- Provide a UI that makes it easy for users to consent to and revoke access. | ||
- Provide a secure way for clients to obtain client secrets for sensitive applications. | ||
- Implement recommended/optional security features, such as scope, to limit client capabilities. | ||
|
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.
How does the MCP Client obtain the
client_id
value? And, what type of OAuth2 client is the MCP Client?It seems like there are two possible approaches:
client_id
is assigned or obtained in this case.2.1. Using the OAuth 2.0 Dynamic Client Registration Protocol (disadvantage: not many Authorization Servers seem to support this).
2.2. Leveraging a developer portal provided by the Authorization Server.