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

[proposal] Add auth as a capability with support for OAuth 2.0 and secrets. #101

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/specification/revisions/draft/_index.md
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.
71 changes: 71 additions & 0 deletions docs/specification/revisions/draft/auth/_index.md
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
243 changes: 243 additions & 0 deletions docs/specification/revisions/draft/auth/oauth2.md
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
Comment on lines +78 to +81

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:

  1. Treat the MCP Client as a public client and use the Authorization Code Flow with PKCE. However, you still need to clarify how the client_id is assigned or obtained in this case.
  2. Treat the MCP Client as a third-party client, where clients are created through one of the following methods:
    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.

"redirect_uri": "redirect_uri", // OPTIONAL
"scope": "scope", // OPTIONAL
"state": "state", // RECOMMENDED
}
}
```

Choose a reason for hiding this comment

The 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 authorization_url that the client redirects the user to (presumably by opening up a browser).

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:

HTTP /authorize?response_type=code&...
Host: mcp.example.com

Choose a reason for hiding this comment

The 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" }
       }
     }
   }
 }

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

The 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"
}
}
```

Choose a reason for hiding this comment

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

Copy link

@nick-merrill nick-merrill Jan 3, 2025

Choose a reason for hiding this comment

The 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 _meta.auth.oauth2 object to provide the access token on requests to the MCP server.


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.

Choose a reason for hiding this comment

The 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:

  • I believe jsonrpc doesn't define how auth works, and stdio doesn't have a side channel information like HTTP request headers
  • Assumption that MCP servers are an OAuth client for some third party service

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.

Choose a reason for hiding this comment

The 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 Authorization header for the token), and then just specify how to pass the token in the stdio transport (e.g. via the _meta.auth.oauth2 object). If we're supporting OAuth2, we might as well get all the benefits of conforming to the standard.

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.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"node": ">=20"
},
"scripts": {
"validate:schema": "tsc --noEmit schema/schema.ts",
"generate:json": "typescript-json-schema --defaultNumberType integer --required schema/schema.ts \"*\" -o schema/schema.json",
"validate:schema": "tsc --noEmit schema/**/*.ts",
"generate:json": "find schema -name '*.ts' -type f -exec sh -c 'typescript-json-schema --defaultNumberType integer --required \"{}\" \"*\" -o \"$(dirname {})/$(basename {} .ts).json\"' \\;",
"serve:docs": "hugo --source site/ server --logLevel debug --disableFastRender"
},
"devDependencies": {
Expand Down
Loading
Loading