Skip to content

uhussain/mcp-client-dynamic

Repository files navigation

MCP Client with Dynamic Registration

A proof-of-concept implementation of an MCP (Model Context Protocol) client that supports dynamic client registration per the MCP Authorization Specification.

Features

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 resource parameter 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-Authenticate headers for 401 error handling

Multi-Server Session Management

  • Independent token management per server
  • Thread-safe in-memory token storage
  • Automatic scope detection and persistence

Architecture

┌─────────────────────┐
│   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

File Structure

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

Installation

Requirements

python3 -m venv grounded
source grounded/bin/activate
pip install flask requests

Python Version

  • Python 3.9+

Quick Start

1. Start the Mock Authorization Server

Terminal 1:

python mock_auth_server.py

This starts an OAuth server on http://localhost:5000 with:

  • Server metadata endpoint
  • Dynamic client registration
  • Token endpoint (with refresh support)

2. Start Mock Resource Servers

Terminal 2 (Slack):

python mock_resource_server.py slack 5001

Terminal 3 (Jira):

python mock_resource_server.py jira 5002

Terminal 4 (GitHub):

python mock_resource_server.py github 5003

3. Run Tests

Terminal 5:

python test_flow.py

This runs 5 comprehensive tests:

  1. Basic Flow: Dynamic registration → OAuth → Resource access
  2. Lazy Registration: Multiple servers registered on-demand
  3. Token Reuse: Cached tokens used for subsequent requests
  4. Token Refresh: Expired tokens automatically refreshed
  5. Multi-Server Independence: Tokens managed separately per server

How it works: Flow Diagram

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

Key Components

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-Authenticate headers 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

Real-World Testing with Stytch & Cloudflare

To validate the MCP client against a production OAuth provider, deployed a real MCP resource server to Cloudflare Workers using Stytch for authentication.

Setup

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

Results

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

⚠️ Limitation Discovered

  • 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

Test MCP Client with Cloudflare-deployed MCP Server using Stytch OAuth

python test_stytch_production.py # Test script for production validation

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages