This document describes how to use the OAuth test server for manual testing and E2E test development.
# Basic start on port 9000
go run ./tests/oauthserver/cmd/server -port 9000
# With longer token expiration (recommended for manual testing)
go run ./tests/oauthserver/cmd/server -port 9000 -access-token-ttl=1hAdd the OAuth test server to your ~/.mcpproxy/mcp_config.json:
{
"mcpServers": [
{
"name": "oauth-test-server",
"url": "http://127.0.0.1:9000/mcp",
"protocol": "streamable-http",
"enabled": true
}
]
}# Via CLI (opens browser automatically)
mcpproxy auth login --server=oauth-test-server
# Or restart mcpproxy to trigger connection
mcpproxy upstream restart oauth-test-server# Check server status
mcpproxy upstream list
# List available tools
mcpproxy tools list --server=oauth-test-serverWhen started, the server displays all configuration:
========================================
OAuth Test Server
========================================
Listening on: http://localhost:9000
Issuer: http://127.0.0.1:9000
Endpoints:
Authorization: http://127.0.0.1:9000/authorize
Token: http://127.0.0.1:9000/token
JWKS: http://127.0.0.1:9000/jwks.json
Discovery: http://127.0.0.1:9000/.well-known/oauth-authorization-server
Protected: http://127.0.0.1:9000/protected
DCR: http://127.0.0.1:9000/registration
Device Auth: http://127.0.0.1:9000/device_authorization
Features:
Auth Code: enabled
Device Code: enabled (RFC 8628)
DCR: enabled (RFC 7591)
Client Creds: enabled
Refresh Token: enabled
PKCE Required: enabled (RFC 7636)
Resource Req: disabled (RFC 8707)
Detection Mode: both
Test Credentials: testuser / testpass
========================================
-port 9000 # Listen port (default: 9000)-no-auth-code # Disable authorization code flow
-no-device-code # Disable device code flow (RFC 8628)
-no-dcr # Disable dynamic client registration (RFC 7591)
-no-client-credentials # Disable client credentials flow
-no-refresh-token # Disable refresh tokens-require-pkce=true # Require PKCE for auth code flow (default: true)
-require-resource=false # Require RFC 8707 resource indicator (default: false)
-runlayer-mode=false # Mimic Runlayer strict validation with Pydantic 422 errors-access-token-ttl=1h # Access token expiry (default: 3m for testing)
-refresh-token-ttl=24h # Refresh token expiry (default: 24h)Controls how OAuth discovery works:
-detection=discovery # Only via /.well-known/oauth-authorization-server
-detection=www-authenticate # Only via WWW-Authenticate header on /protected
-detection=explicit # Requires explicit OAuth config (no auto-detection)
-detection=both # Both discovery and WWW-Authenticate (default)-token-error=invalid_client # Inject error on token endpoint
-token-error=invalid_grant
-token-error=invalid_scope
-token-error=server_error
-auth-error=access_denied # Inject error on authorization endpoint
-auth-error=invalid_requestgo run ./tests/oauthserver/cmd/server -port 9000 \
-no-dcr \
-no-device-code \
-no-client-credentialsgo run ./tests/oauthserver/cmd/server -port 9000 \
-access-token-ttl=24h \
-refresh-token-ttl=168hgo run ./tests/oauthserver/cmd/server -port 9000 \
-require-resource=trueMimics Runlayer's strict OAuth validation with Pydantic-style 422 errors:
go run ./tests/oauthserver/cmd/server -port 9000 -runlayer-modeWhen the resource parameter is missing, returns:
{
"detail": [
{
"type": "missing",
"loc": ["query", "resource"],
"msg": "Field required"
}
]
}This mode implies -require-resource=true.
# Test invalid_grant error on token exchange
go run ./tests/oauthserver/cmd/server -port 9000 \
-token-error=invalid_grantThe login page features automatic submission for E2E testing:
- Pre-filled credentials:
testuser/testpass - 5-second countdown timer with visual progress bar
- Cancel button to stop auto-submit for manual testing
- Auto-cancels if there's an error displayed
This enables fully automated OAuth E2E tests without manual interaction.
The OAuth E2E tests use Playwright to automate browser-based OAuth flows:
# Run OAuth E2E test suite
./scripts/run-oauth-e2e.sh
# Or run directly with Playwright
OAUTH_SERVER_URL="http://127.0.0.1:9000" \
OAUTH_CLIENT_ID="test-client" \
MCPPROXY_URL="http://127.0.0.1:8085" \
MCPPROXY_API_KEY="test-api-key" \
npx playwright test tests/e2e/oauth.spec.ts# Run OAuth server unit tests
go test ./tests/oauthserver/... -v
# Run integration tests (starts test server automatically)
OAUTH_INTEGRATION_TESTS=1 go test ./tests/oauthserver/... -run TestIntegration -vcurl -s http://127.0.0.1:9000/.well-known/oauth-authorization-server | jq .curl -s -X POST http://127.0.0.1:9000/registration \
-H "Content-Type: application/json" \
-d '{
"client_name": "Test Client",
"redirect_uris": ["http://127.0.0.1/callback"],
"grant_types": ["authorization_code", "refresh_token"]
}' | jq .CLIENT_ID="your-client-id"
CLIENT_SECRET="your-client-secret"
curl -s -X POST http://127.0.0.1:9000/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d "grant_type=client_credentials&scope=read write" | jq .ACCESS_TOKEN="your-access-token"
# Should return 401 without token
curl -s -X POST http://127.0.0.1:9000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
# Should succeed with token
curl -s -X POST http://127.0.0.1:9000/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | jq .# List tools
curl -s -X POST http://127.0.0.1:9000/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' | jq .
# Call echo tool
curl -s -X POST http://127.0.0.1:9000/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo","arguments":{"message":"Hello"}}}' | jq .
# Call get_time tool
curl -s -X POST http://127.0.0.1:9000/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"get_time","arguments":{}}}' | jq .The OAuth test server exposes two simple tools for testing:
| Tool | Description | Arguments |
|---|---|---|
echo |
Echoes back the input message | message (string, required) |
get_time |
Returns current server time | none |
If the server remains disconnected after successful OAuth login:
- Check if token expired (default 3-minute TTL)
- Restart with longer TTL:
-access-token-ttl=1h - Clear stale tokens and re-authenticate
Check these environment variables:
HEADLESS=trueprevents browser openingNO_BROWSER=trueprevents browser openingCI=trueprevents browser opening
The server requires PKCE by default. Ensure your client:
- Generates a code_verifier (43-128 characters)
- Sends code_challenge = base64url(sha256(code_verifier))
- Uses code_challenge_method=S256
- Verify JWKS endpoint is accessible:
curl http://127.0.0.1:9000/jwks.json - Check token expiration (exp claim)
- Ensure issuer matches server URL
For lightweight testing of issue #271 (RFC 8707 resource parameter), there's also a Python/FastAPI mock server:
test/integration/oauth_runlayer_mock.py
# Using uv (recommended)
PORT=9000 uv run --with fastapi --with uvicorn python test/integration/oauth_runlayer_mock.py
# Using pip
pip install fastapi uvicorn
PORT=9000 python test/integration/oauth_runlayer_mock.py| Scenario | Recommended |
|---|---|
| Full E2E tests with Playwright | Go server |
| Testing OAuth discovery modes | Go server |
| Token refresh/expiry testing | Go server |
| Error injection scenarios | Go server |
| JWKS/key rotation testing | Go server |
| Quick RFC 8707 resource tests | Python or Go with -runlayer-mode |
| Issue #271 regression testing | Python (lighter) or Go |
| CI/CD integration tests | Go (embeddable) |
./test/integration/test_oauth_resource_injection.shThis script:
- Starts the Python mock server
- Starts mcpproxy with test configuration
- Verifies mcpproxy injects the
resourceparameter correctly - Confirms the auth URL is accepted by the mock server