A debugging/testing server for MCP (Model Context Protocol) clients. Implements a full OAuth 2.0 Authorization Server with PKCE support. Logs and echoes all incoming HTTP requests, including headers, body, and connection metadata.
- Complete OAuth 2.0 Implementation: Authorization Code flow with PKCE
- Dynamic Client Registration: RFC 7591 support for automatic client registration
- Complete Request & Response Logging: Logs all HTTP headers, body, status codes
- Real-time Log Streaming: View logs instantly in terminal
- MCP Protocol Support: Handles standard MCP JSON-RPC methods with mock responses
- Custom Token Support: Enter your own tokens for end-to-end tracing
- No Real Users Required: "Login" with a single click - no username/password needed
- CORS Enabled: Works with browser-based MCP clients
# Install dependencies
npm install
# Start server (auth optional)
npm run local
# Start server with auth REQUIRED on MCP endpoints
npm run local:auth
# Expose via cloudflared tunnel
npm run tunnel:authThe server implements the full OAuth 2.0 Authorization Code flow with PKCE:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β OAuth 2.0 Authorization Code Flow β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
MCP Client Echo Server User
β β β
β 1. GET /authorize β β
β ?client_id=... β β
β &redirect_uri=... β β
β &code_challenge=... β β
β &state=... β β
β ββββββββββββββββββββββββββββββββββΊ β β
β β β
β β 2. Show Authorization β
β β Page β
β β βββββββββββββββββββββββββΊ β
β β β
β β 3. User clicks β
β β "Approve" β
β β (+ optional custom β
β β token) β
β β βββββββββββββββββββββββββ β
β β β
β 4. Redirect to redirect_uri β β
β ?code=ABC123 β β
β &state=... β β
β ββββββββββββββββββββββββββββββββββ β β
β β β
β 5. POST /token β β
β grant_type=authorization_code β β
β code=ABC123 β β
β code_verifier=... β β
β ββββββββββββββββββββββββββββββββββΊ β β
β β β
β 6. Token Response β β
β { access_token: "user-token" } β β
β ββββββββββββββββββββββββββββββββββ β β
β β β
β 7. MCP Request with Bearer token β β
β Authorization: Bearer user-tokenβ β
β ββββββββββββββββββββββββββββββββββΊ β β
β β β
The token you choose is not returned directly from the authorization page. Instead:
- Authorization page returns an opaque
code(e.g.,ABC123) - Client exchanges the code at
/tokenendpoint - Token endpoint returns your chosen
access_token
Why this indirection?
- The authorization code travels via browser redirect (URL) - URLs can leak via browser history, referrer headers, logs
- The actual token is returned via a secure POST request (back-channel)
- PKCE (
code_verifier/code_challenge) proves the same client that started the flow is completing it - Authorization codes expire in 60 seconds - even if intercepted, useless without the
code_verifier
| Endpoint | Method | Description |
|---|---|---|
/.well-known/oauth-authorization-server |
GET | OAuth 2.0 server metadata (RFC 8414) |
/register |
POST | Dynamic Client Registration (RFC 7591) |
/authorize |
GET | Authorization page - user approves/denies |
/token |
POST | Exchange authorization code for access token |
/auth |
GET | Token management page (bypass OAuth for testing) |
/auth/register |
POST | Register token directly (bypass OAuth) |
/auth/tokens |
GET | List all active tokens |
/auth/revoke |
POST | Revoke a token |
The authorization page lets you enter a custom token instead of using a random one:
- MCP client redirects to
/authorize?... - Authorization page shows with a pre-filled random token
- Replace it with your own memorable token like
my-test-abc - Click "Approve"
- After code exchange,
/tokenreturns{ access_token: "my-test-abc" } - All subsequent MCP requests show
Authorization: Bearer my-test-abcin logs
This makes it trivial to trace requests across the entire OAuth + MCP flow.
| Endpoint | Method | Description |
|---|---|---|
/ |
POST | MCP JSON-RPC endpoint |
/mcp |
POST | Explicit MCP JSON-RPC endpoint |
/sse |
GET | Server-Sent Events stream |
/* |
ANY | Echo endpoint - returns request details |
# 1. Generate PKCE values
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=/+' | head -c 43)
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | base64 | tr '/+' '_-' | tr -d '=')
# 2. Open authorization URL in browser
echo "Open: http://localhost:8787/authorize?client_id=test&redirect_uri=http://localhost:9999/callback&response_type=code&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=xyz"
# 3. After approving, extract the 'code' from the redirect URL
# 4. Exchange code for token
curl -X POST http://localhost:8787/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&code=YOUR_CODE_HERE&redirect_uri=http://localhost:9999/callback&code_verifier=$CODE_VERIFIER"
# 5. Use the token for MCP requests
curl -X POST http://localhost:8787/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'For quick testing without the full OAuth flow:
# Register a token directly
curl -X POST http://localhost:8787/auth/register \
-H "Content-Type: application/json" \
-d '{"token": "my-quick-token"}'
# Use it
curl -X POST http://localhost:8787/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer my-quick-token" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'The server logs both requests and responses with color-coded output:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MCP Echo Server - OAuth 2.0 Authorization Server β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Server running at: http://localhost:8787
π Authentication: REQUIRED
MCP endpoints require valid Bearer token
OAuth 2.0 Endpoints:
GET /.well-known/oauth-authorization-server
GET /authorize - Authorization page
POST /token - Token exchange
Token Management:
GET /auth - Token management page
POST /auth/register - Direct token registration
MCP Endpoints:
POST /mcp - MCP JSON-RPC
GET /sse - Server-Sent Events
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[2024-01-15T10:30:45.123Z] REQUEST a1b2c3d4...
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
POST /token
βββ Headers βββ
content-type: application/x-www-form-urlencoded
βββ Body βββ
{
"grant_type": "authorization_code",
"code": "abc123...",
"code_verifier": "..."
}
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[a1b2c3d4...] Token request: grant_type=authorization_code
[a1b2c3d4...] PKCE verified successfully
[a1b2c3d4...] Token issued: my-custom-...
βββ Response βββ
Status: 200
Headers:
Content-Type: application/json
Cache-Control: no-store
Body:
{
"access_token": "my-custom-token",
"token_type": "Bearer",
"expires_in": 86400,
"scope": "mcp"
}
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
mcp-echo-server/
βββ src/
β βββ index.js # Cloudflare Worker code
βββ scripts/
β βββ local-server.js # Standalone Node.js server (recommended)
β βββ test-client.js # Test client for MCP requests
β βββ setup.sh # Interactive setup script
βββ wrangler.toml # Cloudflare Worker configuration
βββ package.json
βββ README.md
| Script | Description |
|---|---|
npm run local |
Start local server (auth optional) |
npm run local:auth |
Start local server with auth required |
npm run tunnel |
Local server + cloudflared tunnel |
npm run tunnel:auth |
Local server with auth + tunnel |
npm run dev |
Wrangler local development |
npm run deploy |
Deploy to Cloudflare Workers |
npm run tail |
Stream Worker logs |
| Command | Auth Required? | MCP Request Behavior |
|---|---|---|
npm run local |
No | MCP requests work without any token |
npm run local:auth |
Yes | MCP requests return 401 unless valid Bearer token provided |
Use npm run local when you:
- Just want to see what MCP clients are sending
- Don't need to test authentication
- Want quick debugging without token setup
Use npm run local:auth when you:
- Testing the full OAuth 2.0 authorization flow
- Verifying MCP clients handle authentication correctly
- Want to trace tokens end-to-end through the system
Note: OAuth endpoints (/authorize, /token, etc.) work in both modes. The difference is only whether MCP endpoints (/mcp, /sse) enforce token validation.
node scripts/local-server.js [options]
Options:
--require-auth Require valid Bearer token on MCP endpoints
--port=XXXX Custom port (default: 8787)The server exposes standard OAuth 2.0 metadata at /.well-known/oauth-authorization-server:
{
"issuer": "http://localhost:8787",
"authorization_endpoint": "http://localhost:8787/authorize",
"token_endpoint": "http://localhost:8787/token",
"registration_endpoint": "http://localhost:8787/register",
"token_endpoint_auth_methods_supported": ["none"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256", "plain"],
"scopes_supported": ["mcp"]
}MCP clients can automatically register themselves using the /register endpoint (RFC 7591):
curl -X POST http://localhost:8787/register \
-H "Content-Type: application/json" \
-d '{
"redirect_uris": ["http://localhost:9999/callback"],
"client_name": "My MCP Client"
}'Response:
{
"client_id": "client_abc123...",
"client_secret": "...",
"client_id_issued_at": 1234567890,
"redirect_uris": ["http://localhost:9999/callback"],
"client_name": "My MCP Client",
"grant_types": ["authorization_code"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}- Node.js 18+
- cloudflared (optional, for tunnel)
MIT