Skip to content

Commit 20b5dfc

Browse files
committed
Merge RS/AS in client_credentials example
1 parent d927bc0 commit 20b5dfc

File tree

4 files changed

+69
-170
lines changed

4 files changed

+69
-170
lines changed

examples/servers/simple-auth-client-credentials/README.md

Lines changed: 12 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -24,34 +24,17 @@ export MCP_DISCORD_CLIENT_SECRET="your_client_secret_here"
2424

2525
## Running the Servers
2626

27-
### Step 1: Start Authorization Server
27+
### Step 1: Start Resource Server (MCP Server)
2828

2929
```bash
30-
# Navigate to the simple-auth directory
31-
cd examples/servers/simple-auth
30+
# Navigate to the simple-auth-client-credentials directory
31+
cd examples/servers/simple-auth-client-credentials
3232

33-
# Start Authorization Server on port 9000
34-
uv run mcp-simple-auth-as --port=9000
33+
# Start Resource Server on port 8001
34+
uv run mcp-simple-auth-rs --port=8001 --transport=streamable-http
3535
```
3636

37-
**What it provides:**
38-
39-
- OAuth 2.0 flows (registration, authorization, token exchange)
40-
- Discord OAuth integration for user authentication
41-
42-
---
43-
44-
### Step 2: Start Resource Server (MCP Server)
45-
46-
```bash
47-
# In another terminal, navigate to the simple-auth directory
48-
cd examples/servers/simple-auth
49-
50-
# Start Resource Server on port 8001, connected to Authorization Server
51-
uv run mcp-simple-auth-rs --port=8001 --auth-server=http://localhost:9000 --transport=streamable-http
52-
```
53-
54-
### Step 3: Test with Client
37+
### Step 2: Test with Client
5538

5639
```bash
5740
cd examples/clients/simple-auth-client-client-credentials
@@ -72,21 +55,21 @@ curl http://localhost:8001/.well-known/oauth-protected-resource
7255
```json
7356
{
7457
"resource": "http://localhost:8001",
75-
"authorization_servers": ["http://localhost:9000"]
58+
"authorization_servers": ["http://localhost:8001"]
7659
}
7760
```
7861

7962
**Client → Authorization Server:**
8063

8164
```bash
82-
curl http://localhost:9000/.well-known/oauth-authorization-server
65+
curl http://localhost:8001/.well-known/oauth-authorization-server
8366
```
8467

8568
```json
8669
{
87-
"issuer": "http://localhost:9000",
88-
"authorization_endpoint": "http://localhost:9000/authorize",
89-
"token_endpoint": "http://localhost:9000/token"
70+
"issuer": "http://localhost:8001",
71+
"authorization_endpoint": "https://discord.com/api/v10/oauth2/authorize",
72+
"token_endpoint": "https://discord.com/api/v10/oauth2/token"
9073
}
9174
```
9275

@@ -99,14 +82,5 @@ curl http://localhost:9000/.well-known/oauth-authorization-server
9982
curl -v http://localhost:8001/.well-known/oauth-protected-resource
10083

10184
# Test Authorization Server metadata
102-
curl -v http://localhost:9000/.well-known/oauth-authorization-server
103-
```
104-
105-
### Test Token Introspection
106-
107-
```bash
108-
# After getting a token through OAuth flow:
109-
curl -X POST http://localhost:9000/introspect \
110-
-H "Content-Type: application/x-www-form-urlencoded" \
111-
-d "token=your_access_token"
85+
curl -v http://localhost:8001/.well-known/oauth-authorization-server
11286
```

examples/servers/simple-auth-client-credentials/mcp_simple_auth_client_credentials/auth_server.py

Lines changed: 0 additions & 120 deletions
This file was deleted.

examples/servers/simple-auth-client-credentials/mcp_simple_auth_client_credentials/server.py

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,24 @@
55
python -m mcp_simple_auth.server --port=8001
66
"""
77

8+
import asyncio
89
import logging
910
from typing import Any, Literal
1011

1112
import click
1213
import httpx
1314
from pydantic import AnyHttpUrl
1415
from pydantic_settings import BaseSettings, SettingsConfigDict
16+
from starlette.applications import Starlette
17+
from starlette.routing import Mount, Route
18+
from uvicorn import Config, Server
1519

20+
from mcp.server.auth.handlers.metadata import MetadataHandler
1621
from mcp.server.auth.middleware.auth_context import get_access_token
22+
from mcp.server.auth.routes import cors_middleware
1723
from mcp.server.auth.settings import AuthSettings
1824
from mcp.server.fastmcp.server import FastMCP
25+
from mcp.shared.auth import OAuthMetadata
1926

2027
from .token_verifier import IntrospectionTokenVerifier
2128

@@ -33,9 +40,10 @@ class ResourceServerSettings(BaseSettings):
3340
host: str = "localhost"
3441
port: int = 8001
3542
server_url: AnyHttpUrl = AnyHttpUrl("http://localhost:8001")
43+
transport: Literal["sse", "streamable-http"] = "streamable-http"
3644

3745
# Authorization Server settings
38-
auth_server_url: AnyHttpUrl = AnyHttpUrl("http://localhost:9000")
46+
auth_server_url: AnyHttpUrl = AnyHttpUrl("http://localhost:8001")
3947
auth_server_introspection_endpoint: str = f"{API_ENDPOINT}/oauth2/@me"
4048
auth_server_discord_user_endpoint: str = f"{API_ENDPOINT}/users/@me"
4149

@@ -47,7 +55,7 @@ def __init__(self, **data):
4755
super().__init__(**data)
4856

4957

50-
def create_resource_server(settings: ResourceServerSettings) -> FastMCP:
58+
def create_resource_server(settings: ResourceServerSettings) -> Starlette:
5159
"""
5260
Create MCP Resource Server.
5361
"""
@@ -59,10 +67,8 @@ def create_resource_server(settings: ResourceServerSettings) -> FastMCP:
5967
)
6068

6169
# Create FastMCP server as a Resource Server
62-
app = FastMCP(
70+
resource_server = FastMCP(
6371
name="MCP Resource Server",
64-
host=settings.host,
65-
port=settings.port,
6672
debug=True,
6773
token_verifier=token_verifier,
6874
auth=AuthSettings(
@@ -93,14 +99,14 @@ async def get_discord_user_data() -> dict[str, Any]:
9399

94100
return response.json()
95101

96-
@app.tool()
102+
@resource_server.tool()
97103
async def get_user_profile() -> dict[str, Any]:
98104
"""
99105
Get the authenticated user's Discord profile information.
100106
"""
101107
return await get_discord_user_data()
102108

103-
@app.tool()
109+
@resource_server.tool()
104110
async def get_user_info() -> dict[str, Any]:
105111
"""
106112
Get information about the currently authenticated user.
@@ -121,12 +127,53 @@ async def get_user_info() -> dict[str, Any]:
121127
"authorization_server": str(settings.auth_server_url),
122128
}
123129

130+
# Create Starlette app to mount the MCP server and host RFC8414
131+
# metadata to jump to Discord's authorization server
132+
app = Starlette(
133+
debug=True,
134+
routes=[
135+
Route(
136+
"/.well-known/oauth-authorization-server",
137+
endpoint=cors_middleware(
138+
MetadataHandler(metadata=OAuthMetadata(
139+
issuer=settings.server_url,
140+
authorization_endpoint=AnyHttpUrl(f"{API_ENDPOINT}/oauth2/authorize"),
141+
token_endpoint=AnyHttpUrl(f"{API_ENDPOINT}/oauth2/token"),
142+
token_endpoint_auth_methods_supported=["client_secret_basic"],
143+
response_types_supported=["code"],
144+
grant_types_supported=["client_credentials"],
145+
scopes_supported=["identify"]
146+
)).handle,
147+
["GET", "OPTIONS"],
148+
),
149+
methods=["GET", "OPTIONS"],
150+
),
151+
Mount(
152+
"/",
153+
app=resource_server.streamable_http_app() if settings.transport == "streamable-http" else resource_server.sse_app()
154+
),
155+
],
156+
lifespan=lambda app: resource_server.session_manager.run(),
157+
)
158+
124159
return app
125160

126161

162+
async def run_server(settings: ResourceServerSettings):
163+
mcp_server = create_resource_server(settings)
164+
config = Config(
165+
mcp_server,
166+
host=settings.host,
167+
port=settings.port,
168+
log_level="info",
169+
)
170+
server = Server(config)
171+
await server.serve()
172+
173+
127174
@click.command()
128175
@click.option("--port", default=8001, help="Port to listen on")
129-
@click.option("--auth-server", default="http://localhost:9000", help="Authorization Server URL")
176+
@click.option("--auth-server", default="http://localhost:8001", help="Authorization Server URL")
130177
@click.option(
131178
"--transport",
132179
default="streamable-http",
@@ -153,15 +200,14 @@ def main(port: int, auth_server: str, transport: Literal["sse", "streamable-http
153200
auth_server_url=auth_server_url,
154201
auth_server_introspection_endpoint=f"{API_ENDPOINT}/oauth2/@me",
155202
auth_server_discord_user_endpoint=f"{API_ENDPOINT}/users/@me",
203+
transport=transport,
156204
)
157205
except ValueError as e:
158206
logger.error(f"Configuration error: {e}")
159207
logger.error("Make sure to provide a valid Authorization Server URL")
160208
return 1
161209

162210
try:
163-
mcp_server = create_resource_server(settings)
164-
165211
logger.info("=" * 80)
166212
logger.info("📦 MCP RESOURCE SERVER")
167213
logger.info("=" * 80)
@@ -182,7 +228,7 @@ def main(port: int, auth_server: str, transport: Literal["sse", "streamable-http
182228
logger.info("=" * 80)
183229

184230
# Run the server - this should block and keep running
185-
mcp_server.run(transport=transport)
231+
asyncio.run(run_server(settings))
186232
logger.info("Server stopped")
187233
return 0
188234
except Exception as e:

examples/servers/simple-auth-client-credentials/pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ dependencies = [
1919

2020
[project.scripts]
2121
mcp-simple-auth-rs = "mcp_simple_auth_client_credentials.server:main"
22-
mcp-simple-auth-as = "mcp_simple_auth_client_credentials.auth_server:main"
2322

2423
[build-system]
2524
requires = ["hatchling"]

0 commit comments

Comments
 (0)