This guide explains how to configure and use OAuth 2.1 Dynamic Client Registration with MCP Memory Service to enable Claude Code HTTP transport integration.
The MCP Memory Service now supports OAuth 2.1 Dynamic Client Registration (DCR) as specified in RFC 7591. This enables:
- Claude Code HTTP Transport: Direct integration with Claude Code's team collaboration features
- Automated Client Registration: Clients can register themselves without manual configuration
- Secure Authentication: JWT-based access tokens with proper scope validation
- Backward Compatibility: Existing API key authentication continues to work
Set the OAuth environment variable:
export MCP_OAUTH_ENABLED=true# Start with OAuth enabled
memory launch
# Or with HTTPS (recommended for production)
export MCP_HTTPS_ENABLED=true
export MCP_SSL_CERT_FILE=/path/to/cert.pem
export MCP_SSL_KEY_FILE=/path/to/key.pem
memory launch# Test the OAuth implementation
python tests/integration/test_oauth_flow.py http://localhost:8000| Variable | Default | Description |
|---|---|---|
MCP_OAUTH_ENABLED |
true |
Enable/disable OAuth 2.1 endpoints |
MCP_OAUTH_SECRET_KEY |
Auto-generated | JWT signing key (set for persistence) |
MCP_OAUTH_ISSUER |
Auto-detected | OAuth issuer URL |
MCP_OAUTH_ACCESS_TOKEN_EXPIRE_MINUTES |
60 |
Access token lifetime |
MCP_OAUTH_AUTHORIZATION_CODE_EXPIRE_MINUTES |
10 |
Authorization code lifetime |
MCP_OAUTH_REFRESH_TOKEN_EXPIRE_DAYS |
30 |
Refresh token lifetime (issued only when offline_access scope is requested) |
# Production configuration
export MCP_OAUTH_ENABLED=true
export MCP_OAUTH_SECRET_KEY="your-secure-secret-key-here"
export MCP_OAUTH_ISSUER="https://your-domain.com"
export MCP_HTTPS_ENABLED=true
# Development configuration
export MCP_OAUTH_ENABLED=true
export MCP_OAUTH_ISSUER="http://localhost:8000" # Match server portGET /.well-known/oauth-authorization-server/mcp- OAuth server metadataGET /.well-known/openid-configuration/mcp- OpenID Connect discovery
POST /oauth/register- Dynamic client registrationGET /oauth/authorize- Authorization endpointPOST /oauth/token- Token endpoint
GET /oauth/clients/{client_id}- Client information (debugging)
Claude Code will automatically discover and register with the OAuth server:
- Discovery: Claude Code requests
/.well-known/oauth-authorization-server/mcp - Registration: Automatically registers as an OAuth client
- Authorization: Redirects user for authorization (auto-approved in MVP)
- Token Exchange: Exchanges authorization code for access token
- API Access: Uses Bearer token for all HTTP transport requests
If needed, you can manually configure Claude Code:
{
"memoryService": {
"protocol": "http",
"http": {
"endpoint": "http://localhost:8000", # Use actual server endpoint
"oauth": {
"enabled": true,
"discoveryUrl": "http://localhost:8000/.well-known/oauth-authorization-server/mcp"
}
}
}
}All API endpoints support Bearer token authentication:
# Get access token via OAuth flow
export ACCESS_TOKEN="your-jwt-access-token"
# Use Bearer token for API requests
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
http://localhost:8000/api/memoriesThe OAuth system supports four scopes:
read: Access to read-only endpointswrite: Access to create/update endpointsadmin: Access to administrative endpointsoffline_access: Opt-in signal that the client wants arefresh_tokenalongside the access token (see below). Does not grant additional permissions on its own.
The token endpoint supports the refresh_token grant so long-lived sessions can renew an access token without re-driving the authorization flow.
To receive a refresh_token, include offline_access in the scope parameter of the authorization request. Clients that don't request it keep the existing single-token response — this behavior is opt-in to stay compatible with existing integrations.
# Authorization request — note offline_access in scope
GET /oauth/authorize?response_type=code
&client_id=...
&redirect_uri=...
&scope=read%20write%20offline_access
&code_challenge=...&code_challenge_method=S256The token response then includes refresh_token:
{
"access_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "q8Oj...48-byte-opaque...",
"scope": "read write offline_access"
}Renewing an access token:
curl -X POST http://localhost:8000/oauth/token \
-u "<client_id>:<client_secret>" \
-d "grant_type=refresh_token" \
-d "refresh_token=q8Oj..."Public clients (registered with token_endpoint_auth_method=none) omit the -u credentials; the refresh token itself is the binding.
Rotation (OAuth 2.1 §4.3.1): every successful refresh issues a new refresh_token AND revokes the one that was presented. A stolen refresh token is therefore single-use — if both the legitimate client and an attacker try to use it, one succeeds and the other gets invalid_grant. Always store the latest refresh_token from each response.
Scope on refresh: the scope parameter is optional and may only be a subset of the originally granted scope; requesting a broader scope returns invalid_scope.
API key authentication works without OAuth enabled, perfect for single-user deployments or when you don't need team collaboration features:
# Configure API key
export MCP_API_KEY="your-secret-key"
export MCP_OAUTH_ENABLED=false # OAuth not required
export MCP_ALLOW_ANONYMOUS_ACCESS=false # Require authentication
# Start server
memory launch
# Option 1: X-API-Key header (recommended, more secure)
curl -H "X-API-Key: your-secret-key" \
http://localhost:8000/api/memories
# Option 2: Query parameter (convenient, less secure - avoid in production)
curl "http://localhost:8000/api/memories?api_key=your-secret-key"
# Option 3: Bearer token (backward compatible)
curl -H "Authorization: Bearer your-secret-key" \
http://localhost:8000/api/memoriesWhen to use API Key vs OAuth:
- API Key: Single-user deployments, scripts, local development
- OAuth: Team collaboration, Claude Code HTTP transport, multi-user access
- Use HTTPS: Always enable HTTPS in production
- Set Secret Key: Provide a secure
MCP_OAUTH_SECRET_KEY - Secure Storage: Consider persistent client storage for production
- Rate Limiting: Implement rate limiting on OAuth endpoints
The implementation follows OAuth 2.1 security requirements:
- HTTPS required for non-localhost URLs
- Secure client credential generation
- JWT access tokens with proper validation
- Authorization code expiration
- Proper redirect URI validation
OAuth endpoints return 404:
- Ensure
MCP_OAUTH_ENABLED=true - Restart the server after configuration changes
Claude Code connection fails:
- Check HTTPS configuration for production
- Verify OAuth discovery endpoint responds correctly
- Check server logs for OAuth errors
Invalid token errors:
- Verify
MCP_OAUTH_SECRET_KEYis consistent - Check token expiration times
- Ensure proper JWT format
# Test OAuth discovery
curl http://localhost:8000/.well-known/oauth-authorization-server/mcp
# Test client registration
curl -X POST http://localhost:8000/oauth/register \
-H "Content-Type: application/json" \
-d '{"client_name": "Test Client"}'
# Check server logs
tail -f logs/mcp-memory-service.log | grep -i oauth{
"client_name": "My Application",
"redirect_uris": ["https://myapp.com/callback"],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"scope": "read write"
}{
"client_id": "mcp_client_abc123",
"client_secret": "secret_xyz789",
"redirect_uris": ["https://myapp.com/callback"],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"token_endpoint_auth_method": "client_secret_basic"
}{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read write"
}# Basic OAuth functionality test
python tests/integration/test_oauth_flow.py
# Full test suite
pytest tests/ -k oauth
# Manual testing with curl
./scripts/test_oauth_flow.sh- Update scope definitions in
oauth/models.py - Add scope validation in
oauth/middleware.py - Apply scope requirements to endpoints using
require_scope()
For more information, see the OAuth 2.1 specification and RFC 7591.