Skip to content

Commit 17f9c00

Browse files
ihrprpcarleton
andauthored
MCP server separation into Authorization Server (AS) and Resource Server (RS) roles per spec PR #338 (#982)
Co-authored-by: Paul Carleton <[email protected]>
1 parent d0443a1 commit 17f9c00

File tree

20 files changed

+1851
-1603
lines changed

20 files changed

+1851
-1603
lines changed

README.md

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -423,43 +423,42 @@ The `elicit()` method returns an `ElicitationResult` with:
423423

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

426-
`mcp.server.auth` implements an OAuth 2.0 server interface, which servers can use by
427-
providing an implementation of the `OAuthAuthorizationServerProvider` protocol.
426+
`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.
427+
428+
MCP servers can use authentication by providing an implementation of the `TokenVerifier` protocol:
428429

429430
```python
430431
from mcp import FastMCP
431-
from mcp.server.auth.provider import OAuthAuthorizationServerProvider
432-
from mcp.server.auth.settings import (
433-
AuthSettings,
434-
ClientRegistrationOptions,
435-
RevocationOptions,
436-
)
432+
from mcp.server.auth.provider import TokenVerifier, TokenInfo
433+
from mcp.server.auth.settings import AuthSettings
437434

438435

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

443442

444443
mcp = FastMCP(
445444
"My App",
446-
auth_server_provider=MyOAuthServerProvider(),
445+
token_verifier=MyTokenVerifier(),
447446
auth=AuthSettings(
448-
issuer_url="https://myapp.com",
449-
revocation_options=RevocationOptions(
450-
enabled=True,
451-
),
452-
client_registration_options=ClientRegistrationOptions(
453-
enabled=True,
454-
valid_scopes=["myscope", "myotherscope"],
455-
default_scopes=["myscope"],
456-
),
457-
required_scopes=["myscope"],
447+
issuer_url="https://auth.example.com",
448+
resource_server_url="http://localhost:3001",
449+
required_scopes=["mcp:read", "mcp:write"],
458450
),
459451
)
460452
```
461453

462-
See [OAuthAuthorizationServerProvider](src/mcp/server/auth/provider.py) for more details.
454+
For a complete example with separate Authorization Server and Resource Server implementations, see [`examples/servers/simple-auth/`](examples/servers/simple-auth/).
455+
456+
**Architecture:**
457+
- **Authorization Server (AS)**: Handles OAuth flows, user authentication, and token issuance
458+
- **Resource Server (RS)**: Your MCP server that validates tokens and serves protected resources
459+
- **Client**: Discovers AS through RFC 9728, obtains tokens, and uses them with the MCP server
460+
461+
See [TokenVerifier](src/mcp/server/auth/provider.py) for more details on implementing token validation.
463462

464463
## Running Your Server
465464

examples/clients/simple-auth-client/mcp_simple_auth_client/main.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,7 @@ async def connect(self):
160160
print(f"🔗 Attempting to connect to {self.server_url}...")
161161

162162
try:
163-
# Set up callback server
164-
callback_server = CallbackServer(port=3000)
163+
callback_server = CallbackServer(port=3030)
165164
callback_server.start()
166165

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

176175
client_metadata_dict = {
177176
"client_name": "Simple Auth Client",
178-
"redirect_uris": ["http://localhost:3000/callback"],
177+
"redirect_uris": ["http://localhost:3030/callback"],
179178
"grant_types": ["authorization_code", "refresh_token"],
180179
"response_types": ["code"],
181180
"token_endpoint_auth_method": "client_secret_post",
Lines changed: 100 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,139 @@
1-
# Simple MCP Server with GitHub OAuth Authentication
1+
# MCP OAuth Authentication Demo
22

3-
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.
3+
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.
44

5-
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)
5+
---
66

7-
## Overview
7+
## Setup Requirements
88

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

14+
**Set environment variables:**
15+
```bash
16+
export MCP_GITHUB_CLIENT_ID="your_client_id_here"
17+
export MCP_GITHUB_CLIENT_SECRET="your_client_secret_here"
18+
```
1319

14-
## Prerequisites
15-
16-
1. Create a GitHub OAuth App:
17-
- Go to GitHub Settings > Developer settings > OAuth Apps > New OAuth App
18-
- Application name: Any name (e.g., "Simple MCP Auth Demo")
19-
- Homepage URL: `http://localhost:8000`
20-
- Authorization callback URL: `http://localhost:8000/github/callback`
21-
- Click "Register application"
22-
- Note down your Client ID and Client Secret
20+
---
2321

24-
## Required Environment Variables
22+
## Running the Servers
2523

26-
You MUST set these environment variables before running the server:
24+
### Step 1: Start Authorization Server
2725

2826
```bash
29-
export MCP_GITHUB_GITHUB_CLIENT_ID="your_client_id_here"
30-
export MCP_GITHUB_GITHUB_CLIENT_SECRET="your_client_secret_here"
27+
# Navigate to the simple-auth directory
28+
cd examples/servers/simple-auth
29+
30+
# Start Authorization Server on port 9000
31+
uv run mcp-simple-auth-as --port=9000
3132
```
3233

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

40+
---
3541

36-
## Running the Server
42+
### Step 2: Start Resource Server (MCP Server)
3743

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

41-
# Run the server
42-
uv run mcp-simple-auth
48+
# Start Resource Server on port 8001, connected to Authorization Server
49+
uv run mcp-simple-auth-rs --port=8001 --auth-server=http://localhost:9000 --transport=streamable-http
4350
```
4451

45-
The server will start on `http://localhost:8000`.
46-
47-
### Transport Options
4852

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

51-
#### SSE (Server-Sent Events) - Default
5255
```bash
53-
uv run mcp-simple-auth
54-
# or explicitly:
55-
uv run mcp-simple-auth --transport sse
56+
cd examples/clients/simple-auth-client
57+
# Start client with streamable HTTP
58+
MCP_SERVER_PORT=8001 MCP_TRANSPORT_TYPE=streamable_http uv run mcp-simple-auth-client
5659
```
5760

58-
SSE transport provides endpoint:
59-
- `/sse`
6061

61-
#### Streamable HTTP
62+
## How It Works
63+
64+
### RFC 9728 Discovery
65+
66+
**Client → Resource Server:**
6267
```bash
63-
uv run mcp-simple-auth --transport streamable-http
68+
curl http://localhost:8001/.well-known/oauth-protected-resource
69+
```
70+
```json
71+
{
72+
"resource": "http://localhost:8001",
73+
"authorization_servers": ["http://localhost:9000"]
74+
}
6475
```
6576

66-
Streamable HTTP transport provides endpoint:
67-
- `/mcp`
77+
**Client → Authorization Server:**
78+
```bash
79+
curl http://localhost:9000/.well-known/oauth-authorization-server
80+
```
81+
```json
82+
{
83+
"issuer": "http://localhost:9000",
84+
"authorization_endpoint": "http://localhost:9000/authorize",
85+
"token_endpoint": "http://localhost:9000/token"
86+
}
87+
```
6888

89+
## Legacy MCP Server as Authorization Server (Backwards Compatibility)
6990

70-
This ensures backward compatibility without needing multiple server instances. When using SSE transport (`--transport sse`), only the `/sse` endpoint is available.
91+
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):
7192

72-
## Available Tool
93+
### Running the Legacy Server
7394

74-
### get_user_profile
95+
```bash
96+
# Start legacy authorization server on port 8002
97+
uv run mcp-simple-auth-legacy --port=8002
98+
```
99+
100+
**Differences from the new architecture:**
101+
- **MCP server acts as AS:** The MCP server itself provides OAuth endpoints (old spec behavior)
102+
- **No separate RS:** The server handles both authentication and MCP tools
103+
- **Local token validation:** Tokens are validated internally without introspection
104+
- **No RFC 9728 support:** Does not provide `/.well-known/oauth-protected-resource`
105+
- **Direct OAuth discovery:** OAuth metadata is at the MCP server's URL
75106

76-
The only tool in this simple example. Returns the authenticated user's GitHub profile information.
107+
### Testing with Legacy Server
108+
109+
```bash
110+
# Test with client (will automatically fall back to legacy discovery)
111+
cd examples/clients/simple-auth-client
112+
MCP_SERVER_PORT=8002 MCP_TRANSPORT_TYPE=streamable_http uv run mcp-simple-auth-client
113+
```
77114

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

80-
**Returns**: GitHub user profile data including username, email, bio, etc.
120+
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.
81121

122+
## Manual Testing
82123

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

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

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

0 commit comments

Comments
 (0)