8000 MCP server separation into Authorization Server (AS) and Resource Server (RS) roles per spec PR #338 by ihrpr · Pull Request #982 · modelcontextprotocol/python-sdk · GitHub
[go: up one dir, main page]

Skip to content

MCP server separation into Authorization Server (AS) and Resource Server (RS) roles per spec PR #338 #982

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

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
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
44 changes: 21 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -423,43 +423,41 @@ The `elicit()` method returns an `ElicitationResult` with:

Authentication can be used by servers that want to expose tools accessing protected resources.

`mcp.server.auth` implements an OAuth 2.0 server interface, which servers can use by
providing an implementation of the `OAuthAuthorizationServerProvider` protocol.
`mcp.server.auth` implements OAuth 2.1 resource server functionality, where MCP servers act as Resource Servers (RS) that validate tokens issued by separate Authorization Servers (AS). This follows the [MCP authorization specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) and implements RFC 9728 (Protected Resource Metadata) for AS discovery.

MCP servers can use authentication by providing an implementation of the `TokenVerifier` protocol:

```python
from mcp import FastMCP
from mcp.server.auth.provider import OAuthAuthorizationServerProvider
from mcp.server.auth.settings import (
AuthSettings,
ClientRegistrationOptions,
RevocationOptions,
)
from mcp.server.auth.provider import TokenVerifier, TokenInfo
from mcp.server.auth.settings import AuthSettings


class MyOAuthServerProvider(OAuthAuthorizationServerProvider):
# See an example on how to implement at `examples/servers/simple-auth`
...
class MyTokenVerifier(TokenVerifier):
# Implement token validation logic (typically via token introspection)
async def verify_token(self, token: str) -> TokenInfo:
# Verify with your authorization server
...


mcp = FastMCP(
"My App",
auth_server_provider=MyOAuthServerProvider(),
token_verifier=MyTokenVerifier(),
auth=AuthSettings(
issuer_url="https://myapp.com",
revocation_options=RevocationOptions(
enabled=True,
),
client_registration_options=ClientRegistrationOptions(
enabled=True,
valid_scopes=["myscope", "myotherscope"],
default_scopes=["myscope"],
),
required_scopes=["myscope"],
authorization_servers=["https://auth.example.com"],
required_scopes=["mcp:read", "mcp:write"],
),
)
```

See [OAuthAuthorizationServerProvider](src/mcp/server/auth/provider.py) for more details.
For a complete example with separate Authorization Server and Resource Server implementations, see [`examples/servers/simple-auth/`](examples/servers/simple-auth/).

**Architecture:**
- **Authorization Server (AS)**: Handles OAuth flows, user authentication, and token issuance
- **Resource Server (RS)**: Your MCP server that validates tokens and serves protected resources
- **Client**: Discovers AS through RFC 9728, obtains tokens, and uses them with the MCP server

See [TokenVerifier](src/mcp/server/auth/provider.py) for more details on implementing token validation.

## Running Your Server

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,7 @@ async def connect(self):
print(f"🔗 Attempting to connect to {self.server_url}...")

try:
# Set up callback server
callback_server = CallbackServer(port=3000)
callback_server = CallbackServer(port=3030)
callback_server.start()

async 8000 def callback_handler() -> tuple[str, str | None]:
Expand All @@ -175,7 +174,7 @@ async def callback_handler() -> tuple[str, str | None]:

client_metadata_dict = {
"client_name": "Simple Auth Client",
"redirect_uris": ["http://localhost:3000/callback"],
"redirect_uris": ["http://localhost:3030/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "client_secret_post",
Expand Down
151 changes: 99 additions & 52 deletions examples/servers/simple-auth/README.md
Original file line number Diff line number Diff line change
@@ -1,91 +1,138 @@
# Simple MCP Server with GitHub OAuth Authentication
# MCP OAuth Authentication Demo

This is a simple example of an MCP server with GitHub OAuth authentication. It demonstrates the essential components needed for OAuth integration with just a single tool.
This example demonstrates OAuth 2.0 authentication with the Model Context Protocol using **separate Authorization Server (AS) and Resource Server (RS)** to comply with the new RFC 9728 specification.

This is just an example of a server that uses auth, an official GitHub mcp server is [here](https://github.com/github/github-mcp-server)
---

## Overview
## Setup Requirements

This simple demo to show to set up a server with:
- GitHub OAuth2 authorization flow
- Single tool: `get_user_profile` to retrieve GitHub user information
**Create a GitHub OAuth App:**
- Go to GitHub Settings > Developer settings > OAuth Apps > New OAuth App
- **Authorization callback URL:** `http://localhost:9000/github/callback`
- Note down your **Client ID** and **Client Secret**

**Set environment variables:**
```bash
export MCP_GITHUB_CLIENT_ID="your_client_id_here"
export MCP_GITHUB_CLIENT_SECRET="your_client_secret_here"
```

## Prerequisites

1. Create a GitHub OAuth App:
- Go to GitHub Settings > Developer settings > OAuth Apps > New OAuth App
- Application name: Any name (e.g., "Simple MCP Auth Demo")
- Homepage URL: `http://localhost:8000`
- Authorization callback URL: `http://localhost:8000/github/callback`
- Click "Register application"
- Note down your Client ID and Client Secret
---

## Required Environment Variables
## Running the Servers

You MUST set these environment variables before running the server:
### Step 1: Start Authorization Server

```bash
export MCP_GITHUB_GITHUB_CLIENT_ID="your_client_id_here"
export MCP_GITHUB_GITHUB_CLIENT_SECRET="your_client_secret_here"
# Navigate to the simple-auth directory
cd examples/servers/simple-auth

# Start Authorization Server on port 9000
python -m mcp_simple_auth.auth_server --port=9000
```

The server will not start without these environment variables properly set.
**What it provides:**
- OAuth 2.0 flows (registration, authorization, token exchange)
- GitHub OAuth integration for user authentication
- Token introspection endpoint for Resource Servers (`/introspect`)
- User data proxy endpoint (`/github/user`)

---

## Running the Server
### Step 2: Start Resource Server (MCP Server)

```bash
# Set environment variables first (see above)
# In another terminal, navigate to the simple-auth directory
cd examples/servers/simple-auth

# Run the server
uv run mcp-simple-auth
# Start Resource Server on port 8001, connected to Authorization Server
python -m mcp_simple_auth.server --port=8001 --auth-server=http://localhost:9000 --transport=streamable-http
```

The server will start on `http://localhost:8000`.

### Transport Options

This server supports multiple transport protocols that can run on the same port:
### Step 3: Test with Client

#### SSE (Server-Sent Events) - Default
```bash
uv run mcp-simple-auth
# or explicitly:
uv run mcp-simple-auth --transport sse
cd examples/clients/simple-auth-client
# Start client with streamable HTTP
MCP_SERVER_PORT=8001 MCP_TRANSPORT_TYPE=streamable_http python -m mcp_simple_auth_client.main
```

SSE transport provides endpoint:
- `/sse`

#### Streamable HTTP
## How It Works

### RFC 9728 Discovery

**Client → Resource Server:**
```bash
uv run mcp-simple-auth --transport streamable-http
curl http://localhost:8001/.well-known/oauth-protected-resource
```
```json
{
"resource": "http://localhost:8001",
"authorization_servers": ["http://localhost:9000"]
}
```

Streamable HTTP transport provides endpoint:
- `/mcp`
**Client → Authorization Server:**
```bash
curl http://localhost:9000/.well-known/oauth-authorization-server
```
```json
{
"issuer": "http://localhost:9000",
"authorization_endpoint": "http://localhost:9000/authorize",
"token_endpoint": "http://localhost:9000/token"
}
```

## Legacy MCP Server as Authorization Server (Backwards Compatibility)

This ensures backward compatibility without needing multiple server instances. When using SSE transport (`--transport sse`), only the `/sse` endpoint is available.
For backwards compatibility with older MCP implementations, a legacy server is provided that acts as an Authorization Server (following the old spec where MCP servers could optionally provide OAuth):

## Available Tool
### Running the Legacy Server

### get_user_profile
```bash
# Start legacy authorization server on port 8002
python -m mcp_simple_auth.legacy_as_server --port=8002
```

**Differences from the new architecture:**
- **MCP server acts as AS:** The MCP server itself provides OAuth endpoints (old spec behavior)
- **No separate RS:** The server handles both authentication and MCP tools
- **Local token validation:** Tokens are validated internally without introspection
- **No RFC 9728 support:** Does not provide `/.well-known/oauth-protected-resource`
- **Direct OAuth discovery:** OAuth metadata is at the MCP server's URL

The only tool in this simple example. Returns the authenticated user's GitHub profile information.
### Testing with Legacy Server

```bash
# Test with client (will automatically fall back to legacy discovery)
MCP_SERVER_PORT=8002 MCP_TRANSPORT_TYPE=streamable_http python -m mcp_simple_auth_client.main
```

**Required scope**: `user`
The client will:
1. Try RFC 9728 discovery at `/.well-known/oauth-protected-resource` (404 on legacy server)
2. Fall back to direct OAuth discovery at `/.well-known/oauth-authorization-server`
3. Complete authentication with the MCP server acting as its own AS

**Returns**: GitHub user profile data including username, email, bio, etc.
This ensures existing MCP servers (which could optionally act as Authorization Servers under the old spec) continue to work while the ecosystem transitions to the new architecture where MCP servers are Resource Servers only.

## Manual Testing

## Troubleshooting
### Test Discovery
```bash
# Test Resource Server discovery endpoint (new architecture)
curl -v http://localhost:8001/.well-known/oauth-protected-resource

If the server fails to start, check:
1. Environment variables `MCP_GITHUB_GITHUB_CLIENT_ID` and `MCP_GITHUB_GITHUB_CLIENT_SECRET` are set
2. The GitHub OAuth app callback URL matches `http://localhost:8000/github/callback`
3. No other service is using port 8000
4. The transport specified is valid (`sse` or `streamable-http`)
# Test Authorization Server metadata
curl -v http://localhost:9000/.well-known/oauth-authorization-server
```

You can use [Inspector](https://github.com/modelcontextprotocol/inspector) to test Auth
### Test Token Introspection
```bash
# After getting a token through OAuth flow:
curl -X POST http://localhost:9000/introspect \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=your_access_token"
```
Loading
Loading
0