Skip to content

Commit 1a5f104

Browse files
committed
Revert "Merge RS/AS in client_credentials example"
This reverts commit 20b5dfc.
1 parent 20b5dfc commit 1a5f104

File tree

4 files changed

+170
-69
lines changed

4 files changed

+170
-69
lines changed

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

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

2525
## Running the Servers
2626

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

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

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

37-
### Step 2: Test with Client
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
3855

3956
```bash
4057
cd examples/clients/simple-auth-client-client-credentials
@@ -55,21 +72,21 @@ curl http://localhost:8001/.well-known/oauth-protected-resource
5572
```json
5673
{
5774
"resource": "http://localhost:8001",
58-
"authorization_servers": ["http://localhost:8001"]
75+
"authorization_servers": ["http://localhost:9000"]
5976
}
6077
```
6178

6279
**Client → Authorization Server:**
6380

6481
```bash
65-
curl http://localhost:8001/.well-known/oauth-authorization-server
82+
curl http://localhost:9000/.well-known/oauth-authorization-server
6683
```
6784

6885
```json
6986
{
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"
87+
"issuer": "http://localhost:9000",
88+
"authorization_endpoint": "http://localhost:9000/authorize",
89+
"token_endpoint": "http://localhost:9000/token"
7390
}
7491
```
7592

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

84101
# Test Authorization Server metadata
85-
curl -v http://localhost:8001/.well-known/oauth-authorization-server
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"
86112
```
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""
2+
Authorization Server for MCP Split Demo.
3+
4+
This server handles OAuth flows, client registration, and token issuance.
5+
Can be replaced with enterprise authorization servers like Auth0, Entra ID, etc.
6+
7+
NOTE: this is a simplified example for demonstration purposes.
8+
This is not a production-ready implementation.
9+
10+
Usage:
11+
python -m mcp_simple_auth.auth_server --port=9000
12+
"""
13+
14+
import asyncio
15+
import logging
16+
17+
import click
18+
from pydantic import AnyHttpUrl, BaseModel
19+
from starlette.applications import Starlette
20+
from starlette.endpoints import HTTPEndpoint
21+
from starlette.requests import Request
22+
from starlette.responses import JSONResponse, Response
23+
from starlette.routing import Route
24+
from uvicorn import Config, Server
25+
26+
from mcp.server.auth.handlers.metadata import MetadataHandler
27+
from mcp.server.auth.routes import cors_middleware, create_auth_routes
28+
from mcp.server.auth.settings import AuthSettings, ClientRegistrationOptions
29+
from mcp.shared._httpx_utils import create_mcp_http_client
30+
from mcp.shared.auth import OAuthMetadata
31+
32+
logger = logging.getLogger(__name__)
33+
34+
API_BASE = "https://discord.com"
35+
API_ENDPOINT = f"{API_BASE}/api/v10"
36+
37+
38+
class AuthServerSettings(BaseModel):
39+
"""Settings for the Authorization Server."""
40+
41+
# Server settings
42+
host: str = "localhost"
43+
port: int = 9000
44+
server_url: AnyHttpUrl = AnyHttpUrl("http://localhost:9000")
45+
46+
def create_authorization_server(server_settings: AuthServerSettings) -> Starlette:
47+
"""Create the Authorization Server application."""
48+
49+
routes = [
50+
# Create RFC 8414 authorization server metadata endpoint
51+
Route(
52+
"/.well-known/oauth-authorization-server",
53+
endpoint=cors_middleware(
54+
MetadataHandler(metadata=OAuthMetadata(
55+
issuer=server_settings.server_url,
56+
authorization_endpoint=AnyHttpUrl(f"{API_ENDPOINT}/oauth2/authorize"),
57+
token_endpoint=AnyHttpUrl(f"{API_ENDPOINT}/oauth2/token"),
58+
token_endpoint_auth_methods_supported=["client_secret_basic"],
59+
response_types_supported=["code"],
60+
grant_types_supported=["client_credentials"],
61+
scopes_supported=["identify"]
62+
)).handle,
63+
["GET", "OPTIONS"],
64+
),
65+
methods=["GET", "OPTIONS"],
66+
),
67+
]
68+
69+
return Starlette(routes=routes)
70+
71+
72+
async def run_server(server_settings: AuthServerSettings):
73+
"""Run the Authorization Server."""
74+
auth_server = create_authorization_server(server_settings)
75+
76+
config = Config(
77+
auth_server,
78+
host=server_settings.host,
79+
port=server_settings.port,
80+
log_level="info",
81+
)
82+
server = Server(config)
83+
84+
logger.info("=" * 80)
85+
logger.info("MCP AUTHORIZATION PROXY SERVER")
86+
logger.info("=" * 80)
87+
logger.info(f"Server URL: {server_settings.server_url}")
88+
logger.info("Endpoints:")
89+
logger.info(f" - OAuth Metadata: {server_settings.server_url}/.well-known/oauth-authorization-server")
90+
logger.info("")
91+
logger.info("=" * 80)
92+
93+
await server.serve()
94+
95+
96+
@click.command()
97+
@click.option("--port", default=9000, help="Port to listen on")
98+
def main(port: int) -> int:
99+
"""
100+
Run the MCP Authorization Server.
101+
102+
This server handles OAuth flows and can be used by multiple Resource Servers.
103+
"""
104+
logging.basicConfig(level=logging.INFO)
105+
106+
# Create server settings
107+
host = "localhost"
108+
server_url = f"http://{host}:{port}"
109+
server_settings = AuthServerSettings(
110+
host=host,
111+
port=port,
112+
server_url=AnyHttpUrl(server_url),
113+
)
114+
115+
asyncio.run(run_server(server_settings))
116+
return 0
117+
118+
119+
if __name__ == "__main__":
120+
main() # type: ignore[call-arg]

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

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

8-
import asyncio
98
import logging
109
from typing import Any, Literal
1110

1211
import click
1312
import httpx
1413
from pydantic import AnyHttpUrl
1514
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
1915

20-
from mcp.server.auth.handlers.metadata import MetadataHandler
2116
from mcp.server.auth.middleware.auth_context import get_access_token
22-
from mcp.server.auth.routes import cors_middleware
2317
from mcp.server.auth.settings import AuthSettings
2418
from mcp.server.fastmcp.server import FastMCP
25-
from mcp.shared.auth import OAuthMetadata
2619

2720
from .token_verifier import IntrospectionTokenVerifier
2821

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

4537
# Authorization Server settings
46-
auth_server_url: AnyHttpUrl = AnyHttpUrl("http://localhost:8001")
38+
auth_server_url: AnyHttpUrl = AnyHttpUrl("http://localhost:9000")
4739
auth_server_introspection_endpoint: str = f"{API_ENDPOINT}/oauth2/@me"
4840
auth_server_discord_user_endpoint: str = f"{API_ENDPOINT}/users/@me"
4941

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

5749

58-
def create_resource_server(settings: ResourceServerSettings) -> Starlette:
50+
def create_resource_server(settings: ResourceServerSettings) -> FastMCP:
5951
"""
6052
Create MCP Resource Server.
6153
"""
@@ -67,8 +59,10 @@ def create_resource_server(settings: ResourceServerSettings) -> Starlette:
6759
)
6860

6961
# Create FastMCP server as a Resource Server
70-
resource_server = FastMCP(
62+
app = FastMCP(
7163
name="MCP Resource Server",
64+
host=settings.host,
65+
port=settings.port,
7266
debug=True,
7367
token_verifier=token_verifier,
7468
auth=AuthSettings(
@@ -99,14 +93,14 @@ async def get_discord_user_data() -> dict[str, Any]:
9993

10094
return response.json()
10195

102-
@resource_server.tool()
96+
@app.tool()
10397
async def get_user_profile() -> dict[str, Any]:
10498
"""
10599
Get the authenticated user's Discord profile information.
106100
"""
107101
return await get_discord_user_data()
108102

109-
@resource_server.tool()
103+
@app.tool()
110104
async def get_user_info() -> dict[str, Any]:
111105
"""
112106
Get information about the currently authenticated user.
@@ -127,53 +121,12 @@ async def get_user_info() -> dict[str, Any]:
127121
"authorization_server": str(settings.auth_server_url),
128122
}
129123

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-
159124
return app
160125

161126

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-
174127
@click.command()
175128
@click.option("--port", default=8001, help="Port to listen on")
176-
@click.option("--auth-server", default="http://localhost:8001", help="Authorization Server URL")
129+
@click.option("--auth-server", default="http://localhost:9000", help="Authorization Server URL")
177130
@click.option(
178131
"--transport",
179132
default="streamable-http",
@@ -200,14 +153,15 @@ def main(port: int, auth_server: str, transport: Literal["sse", "streamable-http
200153
auth_server_url=auth_server_url,
201154
auth_server_introspection_endpoint=f"{API_ENDPOINT}/oauth2/@me",
202155
auth_server_discord_user_endpoint=f"{API_ENDPOINT}/users/@me",
203-
transport=transport,
204156
)
205157
except ValueError as e:
206158
logger.error(f"Configuration error: {e}")
207159
logger.error("Make sure to provide a valid Authorization Server URL")
208160
return 1
209161

210162
try:
163+
mcp_server = create_resource_server(settings)
164+
211165
logger.info("=" * 80)
212166
logger.info("📦 MCP RESOURCE SERVER")
213167
logger.info("=" * 80)
@@ -228,7 +182,7 @@ def main(port: int, auth_server: str, transport: Literal["sse", "streamable-http
228182
logger.info("=" * 80)
229183

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

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ 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"
2223

2324
[build-system]
2425
requires = ["hatchling"]

0 commit comments

Comments
 (0)