A proof-of-concept implementation of an MCP (Model Context Protocol) client that supports dynamic client registration per the MCP Authorization Specification.
✅ Dynamic Client Registration (RFC 7591)
- Automatically registers with MCP servers on first use
- No pre-configuration required
✅ Resource Indicators for OAuth 2.0 (RFC 8707)
- Includes
resourceparameter in all OAuth requests - Automatic canonical URI normalization
- Zero-configuration resource URI extraction
✅ Lazy Registration
- Only registers when actually accessing a resource
- Efficient multi-server management
✅ OAuth 2.0 with PKCE (RFC 7636)
- Secure authorization code flow
- PKCE support for enhanced security
✅ Automatic Token Refresh
- Detects expired tokens
- Automatically refreshes using refresh tokens
- Falls back to full OAuth flow if refresh fails
✅ Authorization Server Discovery
- Fetches Protected Resource Metadata (RFC 9728) for authorization server discovery
- Fetches Authorization Server Metadata (RFC 8414)
- Parses
WWW-Authenticateheaders for 401 error handling
✅ Multi-Server Session Management
- Independent token management per server
- Thread-safe in-memory token storage
- Automatic scope detection and persistence
┌─────────────────────┐
│ MCP Client │
│ (mcp_client.py) │
└──────────┬──────────┘
│
├─→ Registration Client
│ ├─ Parse WWW-Authenticate
│ ├─ Fetch server metadata
│ └─ Dynamic registration
│
├─→ OAuth Client
│ ├─ PKCE generation
│ ├─ Authorization code flow
│ └─ Token refresh
│
└─→ Token Store
└─ In-memory token management
mcp-client-dynamic/
├── mcp_client.py # Main MCP client orchestrator
├── registration_client.py # Dynamic client registration (RFC 7591)
├── oauth_client.py # OAuth 2.0 + PKCE client (RFC 7636)
├── token_store.py # Thread-safe token management
├── resource_utils.py # RFC 8707 resource URI utilities
├── scopes.py # Scope configuration per server type
├── mock_auth_server.py # Mock OAuth authorization server
├── mock_resource_server.py # Mock MCP resource server
├── test_flow.py # End-to-end test suite (5 tests)
├── README.md # This file
python3 -m venv grounded
source grounded/bin/activate
pip install flask requests- Python 3.9+
Terminal 1:
python mock_auth_server.pyThis starts an OAuth server on http://localhost:5000 with:
- Server metadata endpoint
- Dynamic client registration
- Token endpoint (with refresh support)
Terminal 2 (Slack):
python mock_resource_server.py slack 5001Terminal 3 (Jira):
python mock_resource_server.py jira 5002Terminal 4 (GitHub):
python mock_resource_server.py github 5003Terminal 5:
python test_flow.pyThis runs 5 comprehensive tests:
- Basic Flow: Dynamic registration → OAuth → Resource access
- Lazy Registration: Multiple servers registered on-demand
- Token Reuse: Cached tokens used for subsequent requests
- Token Refresh: Expired tokens automatically refreshed
- Multi-Server Independence: Tokens managed separately per server
1. Client calls resource
↓
2. No credentials? → Register dynamically
├─ Fetch Protected Resource Metadata (RFC 9728)
├─ Discover authorization server from metadata
├─ Fetch Authorization Server Metadata (RFC 8414)
└─ Register client dynamically (RFC 7591)
↓
3. No token? → OAuth flow with PKCE
├─ Generate PKCE code verifier/challenge (RFC 7636)
├─ Get authorization code with state (CSRF protection)
├─ Exchange code for tokens (includes resource parameter)
└─ Store tokens
↓
4. Token expired? → Refresh
├─ Use refresh_token (with resource parameter)
└─ Get new access_token
↓
5. Make authenticated request
└─ Include Bearer token in Authorization header
1. MCPClient (mcp_client.py)
- Orchestrates the full flow
- Lazy registration with automatic server type detection
- Automatic token refresh
- Multi-server management
2. RegistrationClient (registration_client.py)
- Fetches Protected Resource Metadata (RFC 9728) for authorization server discovery
- Fetches Authorization Server Metadata (RFC 8414)
- Registers clients dynamically (RFC 7591)
- Parses
WWW-Authenticateheaders for 401 error handling
3. OAuthClient (oauth_client.py)
- Generates PKCE code verifier/challenge (RFC 7636)
- Completes authorization code flow
- Handles token refresh with resource parameter
4. ResourceUtils (resource_utils.py)
- Canonical URI normalization per RFC 8707
- Automatic resource URI extraction from server URLs
- Validates and formats resource parameters
5. TokenStore (token_store.py)
- Thread-safe in-memory storage
- Tracks token expiration with 60-second buffer
- Manages client credentials with scopes per server
- Stores tokens independently per server URL
To validate the MCP client against a production OAuth provider, deployed a real MCP resource server to Cloudflare Workers using Stytch for authentication.
MCP Resource Server (stytch-mcp-server/)
- Deployed to Cloudflare Workers:
https://stytch-mcp-server.usama-mcp.workers.dev - Implements MCP resource endpoints (
/mcp/tools,/mcp/resources) - Returns RFC 9728 Protected Resource Metadata
- Uses Stytch (
https://six-acoustic-0961.customers.stytch.dev) as OAuth provider
Browser-Based OAuth Flow
- Auto-detects production vs. mock servers (by checking if auth endpoint is localhost)
- Mock servers: Uses programmatic POST flow for automated testing
- Production servers: Launches browser, starts local callback server on
localhost:8080 - Implements full PKCE flow with state validation
✅ Dynamic Registration Verified
📝 Registering new client at https://six-acoustic-0961.customers.stytch.dev/v1/oauth2/register
Requested scope: openid profile email offline_access
✅ Client registered successfully
Client ID: connected-app-test-809b099f-ae04-4b8b-a331-00a84b7dd79a
✅ Discovery & Metadata Working
- Successfully fetched Protected Resource Metadata (RFC 9728)
- Discovered Stytch as authorization server
- Retrieved OAuth metadata (RFC 8414)
- Validated all required endpoints
✅ Browser Flow Implemented
- Local HTTP server receives OAuth callbacks
- Browser auto-opens for user authorization
- State parameter validated for CSRF protection
- Authorization code extracted from callback
- Stytch test projects require custom authorization endpoint implementation?
- Dynamic registration succeeds, but full OAuth flow needs production OAuth provider
- TODO: Use production OAuth providers (Auth0, Okta, etc.) or implement custom auth UI
python test_stytch_production.py # Test script for production validation