Skip to content

Commit fd75834

Browse files
authored
Merge branch 'main' into lifespan_ctx_readme
2 parents 4bddade + c8bbfc0 commit fd75834

File tree

34 files changed

+3542
-1596
lines changed

34 files changed

+3542
-1596
lines changed

.github/workflows/publish-docs-manually.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ jobs:
3030
mkdocs-material-
3131
3232
- run: uv sync --frozen --group docs
33-
- run: uv run --no-sync mkdocs gh-deploy --force
33+
- run: uv run --frozen --no-sync mkdocs gh-deploy --force

.github/workflows/publish-pypi.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
run: uv python install 3.12
2323

2424
- name: Build
25-
run: uv build
25+
run: uv build --frozen
2626

2727
- name: Upload artifacts
2828
uses: actions/upload-artifact@v4
@@ -79,4 +79,4 @@ jobs:
7979
mkdocs-material-
8080
8181
- run: uv sync --frozen --group docs
82-
- run: uv run --no-sync mkdocs gh-deploy --force
82+
- run: uv run --frozen --no-sync mkdocs gh-deploy --force

.github/workflows/shared.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@ jobs:
4444
run: uv sync --frozen --all-extras --python ${{ matrix.python-version }}
4545

4646
- name: Run pytest
47-
run: uv run --no-sync pytest
47+
run: uv run --frozen --no-sync pytest
4848
continue-on-error: true

README.md

Lines changed: 83 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

@@ -830,6 +829,67 @@ if __name__ == "__main__":
830829

831830
Caution: The `mcp run` and `mcp dev` tool doesn't support low-level server.
832831

832+
#### Structured Output Support
833+
834+
The low-level server supports structured output for tools, allowing you to return both human-readable content and machine-readable structured data. Tools can define an `outputSchema` to validate their structured output:
835+
836+
```python
837+
from types import Any
838+
839+
import mcp.types as types
840+
from mcp.server.lowlevel import Server
841+
842+
server = Server("example-server")
843+
844+
845+
@server.list_tools()
846+
async def list_tools() -> list[types.Tool]:
847+
return [
848+
types.Tool(
849+
name="calculate",
850+
description="Perform mathematical calculations",
851+
inputSchema={
852+
"type": "object",
853+
"properties": {
854+
"expression": {"type": "string", "description": "Math expression"}
855+
},
856+
"required": ["expression"],
857+
},
858+
outputSchema={
859+
"type": "object",
860+
"properties": {
861+
"result": {"type": "number"},
862+
"expression": {"type": "string"},
863+
},
864+
"required": ["result", "expression"],
865+
},
866+
)
867+
]
868+
869+
870+
@server.call_tool()
871+
async def call_tool(name: str, arguments: dict[str, Any]) -> dict[str, Any]:
872+
if name == "calculate":
873+
expression = arguments["expression"]
874+
try:
875+
result = eval(expression) # Use a safe math parser
876+
structured = {"result": result, "expression": expression}
877+
878+
# low-level server will validate structured output against the tool's
879+
# output schema, and automatically serialize it into a TextContent block
880+
# for backwards compatibility with pre-2025-06-18 clients.
881+
return structured
882+
except Exception as e:
883+
raise ValueError(f"Calculation error: {str(e)}")
884+
```
885+
886+
Tools can return data in three ways:
887+
1. **Content only**: Return a list of content blocks (default behavior before spec revision 2025-06-18)
888+
2. **Structured data only**: Return a dictionary that will be serialized to JSON (Introduced in spec revision 2025-06-18)
889+
3. **Both**: Return a tuple of (content, structured_data) preferred option to use for backwards compatibility
890+
891+
When an `outputSchema` is defined, the server automatically validates the structured output against the schema. This ensures type safety and helps catch errors early.
892+
833893
### Writing MCP Clients
834894

835895
The SDK provides a high-level client interface for connecting to MCP servers using various [transports](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports):

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: 91 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,129 @@
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
87

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
8+
## Running the Servers
129

10+
### Step 1: Start Authorization Server
1311

14-
## Prerequisites
12+
```bash
13+
# Navigate to the simple-auth directory
14+
cd examples/servers/simple-auth
15+
16+
# Start Authorization Server on port 9000
17+
uv run mcp-simple-auth-as --port=9000
18+
```
1519

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+
**What it provides:**
21+
- OAuth 2.0 flows (registration, authorization, token exchange)
22+
- Simple credential-based authentication (no external provider needed)
23+
- Token introspection endpoint for Resource Servers (`/introspect`)
2324

24-
## Required Environment Variables
25+
---
2526

26-
You MUST set these environment variables before running the server:
27+
### Step 2: Start Resource Server (MCP Server)
2728

2829
```bash
29-
export MCP_GITHUB_GITHUB_CLIENT_ID="your_client_id_here"
30-
export MCP_GITHUB_GITHUB_CLIENT_SECRET="your_client_secret_here"
31-
```
30+
# In another terminal, navigate to the simple-auth directory
31+
cd examples/servers/simple-auth
3232

33-
The server will not start without these environment variables properly set.
33+
# Start Resource Server on port 8001, connected to Authorization Server
34+
uv run mcp-simple-auth-rs --port=8001 --auth-server=http://localhost:9000 --transport=streamable-http
3435

36+
# With RFC 8707 strict resource validation (recommended for production)
37+
uv run mcp-simple-auth-rs --port=8001 --auth-server=http://localhost:9000 --transport=streamable-http --oauth-strict
3538

36-
## Running the Server
39+
```
3740

38-
```bash
39-
# Set environment variables first (see above)
4041

41-
# Run the server
42-
uv run mcp-simple-auth
42+
### Step 3: Test with Client
43+
44+
```bash
45+
cd examples/clients/simple-auth-client
46+
# Start client with streamable HTTP
47+
MCP_SERVER_PORT=8001 MCP_TRANSPORT_TYPE=streamable_http uv run mcp-simple-auth-client
4348
```
4449

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

47-
### Transport Options
51+
## How It Works
4852

49-
This server supports multiple transport protocols that can run on the same port:
53+
### RFC 9728 Discovery
5054

51-
#### SSE (Server-Sent Events) - Default
55+
**Client → Resource Server:**
5256
```bash
53-
uv run mcp-simple-auth
54-
# or explicitly:
55-
uv run mcp-simple-auth --transport sse
57+
curl http://localhost:8001/.well-known/oauth-protected-resource
58+
```
59+
```json
60+
{
61+
"resource": "http://localhost:8001",
62+
"authorization_servers": ["http://localhost:9000"]
63+
}
5664
```
5765

58-
SSE transport provides endpoint:
59-
- `/sse`
60-
61-
#### Streamable HTTP
66+
**Client → Authorization Server:**
6267
```bash
63-
uv run mcp-simple-auth --transport streamable-http
68+
curl http://localhost:9000/.well-known/oauth-authorization-server
69+
```
70+
```json
71+
{
72+
"issuer": "http://localhost:9000",
73+
"authorization_endpoint": "http://localhost:9000/authorize",
74+
"token_endpoint": "http://localhost:9000/token"
75+
}
6476
```
6577

66-
Streamable HTTP transport provides endpoint:
67-
- `/mcp`
6878

79+
## Legacy MCP Server as Authorization Server (Backwards Compatibility)
6980

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

72-
## Available Tool
83+
### Running the Legacy Server
7384

74-
### get_user_profile
85+
```bash
86+
# Start legacy authorization server on port 8002
87+
uv run mcp-simple-auth-legacy --port=8002
88+
```
89+
90+
**Differences from the new architecture:**
91+
- **MCP server acts as AS:** The MCP server itself provides OAuth endpoints (old spec behavior)
92+
- **No separate RS:** The server handles both authentication and MCP tools
93+
- **Local token validation:** Tokens are validated internally without introspection
94+
- **No RFC 9728 support:** Does not provide `/.well-known/oauth-protected-resource`
95+
- **Direct OAuth discovery:** OAuth metadata is at the MCP server's URL
7596

76-
The only tool in this simple example. Returns the authenticated user's GitHub profile information.
97+
### Testing with Legacy Server
98+
99+
```bash
100+
# Test with client (will automatically fall back to legacy discovery)
101+
cd examples/clients/simple-auth-client
102+
MCP_SERVER_PORT=8002 MCP_TRANSPORT_TYPE=streamable_http uv run mcp-simple-auth-client
103+
```
77104

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

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

112+
## Manual Testing
82113

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

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`)
119+
# Test Authorization Server metadata
120+
curl -v http://localhost:9000/.well-known/oauth-authorization-server
121+
```
90122

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

0 commit comments

Comments
 (0)