@vmenon25/mcp-server-webauthn-client
v0.0.5
Published
MCP server and CLI tool for generating WebAuthn TypeScript web clients
Maintainers
Readme
WebAuthn Client Generator - MCP Server
A Model Context Protocol (MCP) server that helps AI agents automatically generate complete WebAuthn web clients for testing authentication.
Overview
This MCP server provides AI agents with the ability to generate fully-functional WebAuthn web clients that connect to webauthn-server instances.
What Gets Generated
When you use this tool, you get a complete, zero-trust WebAuthn stack:
- ✅ Envoy Gateway - Entry point with JWT authentication for protected routes
- ✅ WebAuthn Server - FIDO2/Passkey authentication + JWT issuer
- ✅ JWKS Endpoint (
/.well-known/jwks.json) - Public key distribution for JWT verification - ✅ Example Service - Python FastAPI demo showing JWT-protected endpoints
- ✅ PostgreSQL 15 - Credential storage with auto-schema initialization
- ✅ Redis 7 - Session management
- ✅ Jaeger - Distributed tracing (optional)
- ✅ Docker Compose - Complete orchestration with secrets management
- ✅ Playwright E2E Tests - Automated testing with Chromium virtual authenticator
- ✅ TypeScript Web Client - Complete build toolchain and dev server
Key Feature: All services communicate through Envoy Gateway using JWT authentication verified via the JWKS endpoint, demonstrating a zero-trust architecture.
Generated Web Client Structure
web-client/
├── .gitignore # 🆕 Comprehensive gitignore for Node/TypeScript
├── package.json # All required dependencies
├── src/
│ ├── index.ts # Entry point
│ ├── webauthn-client.ts # Complete WebAuthn integration
│ ├── types.ts # TypeScript type definitions
│ └── server.ts # Express development server
├── public/
│ └── index.html # Test UI
├── docker/ # 🆕 Complete Docker stack
│ ├── docker-compose.yml # PostgreSQL + Redis + WebAuthn Server
│ ├── init-db.sql # Database schema initialization
│ ├── setup-secrets.sh # Secret verification script
│ └── secrets/ # 🔐 Auto-generated passwords (gitignored)
│ ├── .gitignore # Prevents accidental commits
│ ├── postgres_password # Auto-generated 32-char password
│ └── redis_password # Auto-generated 32-char password
├── tests/ # 🆕 Playwright E2E tests
│ └── webauthn.spec.js # Registration + Authentication tests
├── playwright.config.js # Playwright configuration
├── global-setup.js # Test setup (starts client server)
├── global-teardown.js # Test cleanup
├── webpack.config.js # Build configuration
├── tsconfig.json # TypeScript configuration
├── tsconfig.build.json # Build-specific TypeScript config
└── README.md # Usage instructions + security docsFeatures
Single Focused Tool: generate_web_client
Generates a complete web client with:
- ✅ TypeScript WebAuthn client with all critical integration patterns
- ✅ Express development server (configurable port)
- ✅ Complete build toolchain (Webpack, Babel, TypeScript)
- ✅ Test UI with registration and authentication flows
- ✅ Proper error handling for WebAuthn-specific errors
- ✅ Type-safe implementation using published npm client library
- ✅ Complete Docker stack (PostgreSQL + Redis + WebAuthn Server)
- ✅ Secure password generation using crypto.randomBytes
- ✅ Docker Compose secrets (no hardcoded credentials)
- ✅ Playwright E2E tests with Chromium virtual authenticator
- ✅ Auto-initialized database schema on first startup
Supported Frameworks
- Vanilla TypeScript (default) - Currently implemented
- React - Planned for future release
- Vue - Planned for future release
Installation
Local Installation (Development)
cd mcp-server-webauthn-client
npm install # Automatically builds dist/ via prepare scriptNote: The dist/ directory is automatically built during npm install via the prepare script in package.json. For manual builds:
npm run build # Build once
npm run dev # Watch modeGlobal Installation (For AI Agent Discovery)
npm install -g @vmenon25/mcp-server-webauthn-clientUsage
This package can be used in two ways:
- MCP Mode - For MCP-compatible AI agents (Claude Code, Continue.dev, Cline)
- CLI Mode - For non-MCP agents (Cursor, Aider, Windsurf) or direct usage
MCP Mode (For Claude Code & Compatible Agents)
- Add to your project's
claude_config.json:
{
"mcpServers": {
"webauthn-client-generator": {
"command": "npx",
"args": [
"-y",
"@vmenon25/mcp-server-webauthn-client@latest"
]
}
}
}- Use naturally in conversation:
You: "Please create a webauthn stack using the webauthn-client-generator MCP server."
Claude: I'll generate a complete web client for you using the WebAuthn client generator.
[Claude calls the generate_web_client tool]
✅ Web client generated successfully!
Files created:
- ./web-client/package.json
- ./web-client/src/webauthn-client.ts
- ./web-client/src/types.ts
- ./web-client/src/server.ts
- ./web-client/public/index.html
- ./web-client/webpack.config.js
- ./web-client/tsconfig.json
- ./web-client/README.md
Next steps:
1. cd web-client
2. npm install
3. npm run build
4. npm start
5. Open http://localhost:8082Direct MCP Tool Call
The tool accepts these parameters:
{
// Required
project_path: string; // Where to create web client (e.g., './web-client')
// Core Configuration
framework?: 'vanilla' | 'react' | 'vue'; // Default: 'vanilla'
server_url?: string; // Envoy Gateway URL (default: 'http://localhost:8000')
forward_port?: number; // Client dev server port (default: 8082)
// WebAuthn Configuration
relying_party_id?: string; // WebAuthn RP ID (default: 'localhost')
relying_party_name?: string; // WebAuthn RP name (default: 'WebAuthn Demo')
// Infrastructure Port Customization (to avoid conflicts)
postgres_host_port?: number; // PostgreSQL host port (default: 5432)
redis_host_port?: number; // Redis host port (default: 6379)
gateway_host_port?: number; // Envoy Gateway host port (default: 8000)
gateway_admin_port?: number; // Envoy admin host port (default: 9901)
// Jaeger Tracing Port Customization
jaeger_ui_port?: number; // Jaeger UI port (default: 16686)
jaeger_collector_http_port?: number; // Jaeger collector HTTP (default: 14268)
jaeger_collector_grpc_port?: number; // Jaeger collector gRPC (default: 14250)
jaeger_otlp_grpc_port?: number; // Jaeger OTLP gRPC (default: 4317)
jaeger_otlp_http_port?: number; // Jaeger OTLP HTTP (default: 4318)
jaeger_agent_compact_port?: number; // Jaeger agent compact thrift UDP (default: 6831)
jaeger_agent_binary_port?: number; // Jaeger agent binary thrift UDP (default: 6832)
jaeger_agent_config_port?: number; // Jaeger agent config HTTP (default: 5778)
}CLI Mode (Recommended for Non-MCP Agents)
The easiest way to use the client generator without MCP setup:
# Basic usage (generates in ./web-client)
npx -y @vmenon25/mcp-server-webauthn-client
# Custom path
npx -y @vmenon25/mcp-server-webauthn-client --path ./auth-client
# Custom server URL and forward port
npx -y @vmenon25/mcp-server-webauthn-client \
--server http://localhost:9000 \
--forward-port 3000
# Production server with custom relying party
npx -y @vmenon25/mcp-server-webauthn-client \
--server https://auth.example.com
# Avoid port conflicts with existing infrastructure
npx -y @vmenon25/mcp-server-webauthn-client \
--postgres-host-port 5433 \
--redis-host-port 6380 \
--gateway-host-port 8001
# Show help (lists all available options)
npx -y @vmenon25/mcp-server-webauthn-client --helpCLI Options:
Core Options:
-p, --path <path>- Directory to create web client (default:./web-client)-s, --server <url>- Envoy Gateway URL (default:http://localhost:8000)-P, --forward-port <port>- Client dev server port (default:8082)-f, --framework <name>- Framework:vanilla|react|vue(default:vanilla)-h, --help- Show help message with all options
Infrastructure Port Customization:
--postgres-host-port <port>- PostgreSQL host port (default:5432)--redis-host-port <port>- Redis host port (default:6379)--gateway-host-port <port>- Envoy Gateway host port (default:8000)--gateway-admin-port <port>- Envoy admin host port (default:9901)
Jaeger Tracing Port Customization:
--jaeger-ui-port <port>- Jaeger UI port (default:16686)--jaeger-collector-http-port <port>- Jaeger collector HTTP (default:14268)--jaeger-collector-grpc-port <port>- Jaeger collector gRPC (default:14250)--jaeger-otlp-grpc-port <port>- Jaeger OTLP gRPC (default:4317)--jaeger-otlp-http-port <port>- Jaeger OTLP HTTP (default:4318)--jaeger-agent-compact-port <port>- Jaeger agent compact thrift UDP (default:6831)--jaeger-agent-binary-port <port>- Jaeger agent binary thrift UDP (default:6832)--jaeger-agent-config-port <port>- Jaeger agent config HTTP (default:5778)
JWT Key Rotation Configuration (Advanced):
The generated WebAuthn stack includes automatic JWT signing key rotation for enhanced security (always enabled). These parameters control the rotation behavior using HOCON duration format:
CLI Parameters:
--jwt-rotation-interval <duration>- Time between key rotations (default:180d= 6 months)--jwt-grace-period <duration>- Grace period before retiring old key (default:1h= 1 hour)--jwt-retention <duration>- How long to keep retired keys for verification (default:1h= 1 hour)--jwks-cache-duration-seconds <seconds>- JWKS endpoint cache duration (default:300= 5 minutes)
Duration Format (HOCON Syntax):
- Format:
<number><unit>(e.g.,30s,180d,2h,60m) - Supported units:
s,second,secondsm,minute,minutesh,hour,hoursd,day,days
- Examples:
"30s"(30 seconds),"2h"(2 hours),"180d"(180 days),"1 hour"(1 hour)
Production Example (6-month rotation with 1-hour grace period):
npx -y @vmenon25/mcp-server-webauthn-client \
--jwt-rotation-interval 180d \
--jwt-grace-period 1h \
--jwt-retention 1h \
--jwks-cache-duration-seconds 300Testing Example (accelerated 30-second rotation for E2E tests):
npx -y @vmenon25/mcp-server-webauthn-client \
--jwt-rotation-interval 30s \
--jwt-grace-period 15s \
--jwt-retention 30s \
--jwks-cache-duration-seconds 5How Key Rotation Works:
- Initial State: Server starts with first signing key (K1) in ACTIVE status
- Rotation Trigger: After rotation interval (e.g., 180 days), server generates new key (K2)
- Pending Phase: K2 is PENDING while K1 remains ACTIVE (seamless for existing tokens)
- Activation: After grace period (e.g., 1 hour), K2 becomes ACTIVE, K1 becomes RETIRED
- Cleanup: After retention period (e.g., 1 hour), K1 is deleted from database
- JWKS Endpoint:
/.well-known/jwks.jsonalways serves current ACTIVE + RETIRED keys for verification
Timeline Example (Production Settings):
T+0: K1 ACTIVE → Rotation needed
T+0: K2 PENDING generated
T+1h: K2 ACTIVE, K1 RETIRED (grace period expired)
T+2h: K1 deleted (retention period expired)
T+180d: K3 PENDING generated, cycle repeatsEnvironment Variables (Alternative to CLI):
MPO_AUTHN_JWT_KEY_ROTATION_ENABLED: "true"
MPO_AUTHN_JWT_KEY_ROTATION_INTERVAL: "180d"
MPO_AUTHN_JWT_KEY_GRACE_PERIOD: "1h"
MPO_AUTHN_JWT_KEY_RETENTION: "1h"Why HOCON Format?
- ✅ Human-readable: "180d" is clearer than 15552000 seconds or 0.000347 days
- ✅ Flexible: Same variable works for production (days) and testing (seconds)
- ✅ Standard: Industry-standard format used by Kubernetes, Spring Boot, etc.
- ✅ Safe: Native library parsing with clear error messages
Discovery File for Non-MCP Agents
For coding agents without native MCP support (Cursor, Aider, Windsurf, etc.), this tool can be discovered via the project's .ai-agents.json file:
Discovery URL: https://raw.githubusercontent.com/hitoshura25/mpo-api-authn-server/main/.ai-agents.json
How Non-MCP Agents Discover This Tool:
- Agent reads
.ai-agents.jsonfrom the project root (~1500 tokens one-time cost) - Finds
mcp_servers.webauthn_client_generatorsection with complete usage documentation - Suggests CLI command to user based on their requirements
- User runs command - tool executes locally (0 additional tokens)
Example Discovery Flow:
User: "I need a complete WebAuthn authentication stack for testing passkeys"
Agent (internal):
1. Reads .ai-agents.json from project
2. Finds webauthn-client-generator tool
3. Extracts CLI usage examples
Agent (response):
"I found a WebAuthn stack generator tool. Run this command to generate a complete authentication infrastructure:
npx -y @vmenon25/mcp-server-webauthn-client --path ./web-client
This will generate:
• Complete zero-trust architecture with Envoy Gateway
• WebAuthn Server Docker image (hitoshura25/webauthn-server)
• PostgreSQL + Redis + Jaeger infrastructure
• TypeScript web client with complete build toolchain
• E2E Playwright tests with virtual authenticator
• JWT authentication with JWKS endpoint
After generation:
1. cd web-client/docker && docker compose up -d # Start infrastructure
2. cd .. && npm install && npm run build && npm start
3. npm test # Run E2E tests
4. Open http://localhost:8082What's in .ai-agents.json:
- Complete tool description and usage examples
- All CLI options with defaults
- Critical integration patterns (JSON parsing, credential serialization)
- Docker compose configuration examples
- Common pitfalls and solutions
Benefits for Non-MCP Agents:
- ✅ Accurate instructions: Based on official documentation
- ✅ Complete examples: Working CLI commands ready to use
- ✅ No manual configuration: Single command generates everything
What is MCP?
Model Context Protocol (MCP) is a standard protocol that allows AI agents like Claude to interact with external tools and services. MCP servers expose tools that AI agents can discover and use automatically to help users accomplish tasks.
Zero-Trust Architecture Explained
The generated stack implements a zero-trust architecture using industry-standard patterns:
1. Envoy Gateway as Entry Point
All traffic flows through Envoy Gateway - no direct access to backend services:
Public Routes (no authentication required):
/register/*- WebAuthn registration endpoints/authenticate/*- WebAuthn authentication endpoints/.well-known/jwks.json- Public key distribution (JWKS)/health- Health check endpoint
Protected Routes (JWT authentication required):
/api/*- All application endpoints require valid JWT
2. JWKS Endpoint for JWT Verification
The JWKS (JSON Web Key Set) endpoint (/.well-known/jwks.json) provides public keys for JWT verification:
{
"keys": [{
"kty": "RSA",
"use": "sig",
"kid": "unique-key-id",
"alg": "RS256",
"n": "base64url-encoded-modulus",
"e": "base64url-encoded-exponent"
}]
}How Envoy uses JWKS:
- Envoy Gateway fetches JWKS on startup and caches for 5 minutes
- When request comes to
/api/*, Envoy extracts JWT from Authorization header - Envoy verifies JWT signature using public key from JWKS
- If valid, request forwards to backend service; if invalid, returns 401 Unauthorized
Benefits:
- ✅ JWT verification happens at gateway (zero-trust perimeter)
- ✅ Backend services receive only verified requests
- ✅ Key rotation supported via JWKS updates
- ✅ No shared secrets between services
3. Example Service - Protected Endpoint Demo
The Example Service (Python FastAPI) demonstrates a protected microservice:
# Located at: web-client/example-service/main.py
@app.get("/api/example/data")
async def get_data():
# This endpoint is protected by Envoy Gateway
# JWT has already been verified before request reaches here
return {"message": "Protected data", "timestamp": "..."}Key Points:
- Runs on port 9000 (not directly accessible from outside)
- All requests come through Envoy Gateway
- JWT already verified by Envoy (zero-trust model)
- Service can extract user info from JWT claims
How to add your own protected services:
- Add service definition to
docker/docker-compose.yml - Add route to
docker/envoy-gateway.yamlunder protected routes - Service receives only authenticated requests
4. mTLS Between Services (Phase 2)
The stack includes mTLS (mutual TLS) configuration between Envoy Gateway and backend services:
# envoy-gateway.yaml
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
common_tls_context:
tls_certificates: # Gateway's client cert
validation_context: # Validates service certsBenefits:
- ✅ Encrypted communication between services
- ✅ Mutual authentication (both sides verify identity)
- ✅ Protection against man-in-the-middle attacks
- ✅ Zero-trust within the service mesh
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Generated Stack │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Web Client (TypeScript) │
│ └─ http://localhost:8082 │
│ │ │
│ │ WebAuthn Registration/Authentication │
│ ▼ │
│ ┌────────────────────────────────────────┐ │
│ │ Envoy Gateway (port 8000) │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ Public Routes (no JWT required) │ │ │
│ │ │ ✓ /register/* │ │ │
│ │ │ ✓ /authenticate/* │ │ │
│ │ │ ✓ /.well-known/jwks.json │ │ │
│ │ │ ✓ /health │ │ │
│ │ └──────────────────────────────────┘ │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ Protected Routes (JWT required) │ │ │
│ │ │ ✓ /api/* → JWT verification via │ │ │
│ │ │ JWKS endpoint │ │ │
│ │ └──────────────────────────────────┘ │ │
│ └─────┬──────────────┬─────────────────┘ │
│ │ │ │
│ │ │ mTLS (Phase 2) │
│ ▼ ▼ │
│ ┌────────── ┐ ┌───────────────── ┐ │
│ │ WebAuthn │ │ Example Service │ │
│ │ Server │ │ (Python/FastAPI)│ │
│ │ :8080 │ │ :9000 │ │
│ │ │ │ │ │
│ │ • Issues │ │ • Validates JWT │ │
│ │ JWTs │ │ • Protected │ │
│ │ • JWKS │ │ /api/example/* │ │
│ │ endpoint│ │ endpoints │ │
│ └────┬───── ┘ └───────────────── ┘ │
│ │ │
│ ├─ PostgreSQL 15 (credentials) │
│ ├─ Redis 7 (sessions) │
│ └─ Jaeger (distributed tracing) │
│ │
└─────────────────────────────────────────────────────────────────┘
JWT Authentication Flow:
1. User authenticates via WebAuthn → Server issues JWT
2. Client includes JWT in Authorization header for /api/* requests
3. Envoy Gateway fetches public key from /.well-known/jwks.json
4. Envoy verifies JWT signature using JWKS before forwarding request
5. Protected service receives verified request with JWT
Security Principles:
• Never trust, always verify (even internal traffic)
• JWT verification at perimeter (Envoy Gateway)
• Public key cryptography (JWKS) for verification
• mTLS for inter-service communication
• Secrets management via Docker secretsTroubleshooting
Connection Refused Errors
Issue: ECONNREFUSED when accessing http://localhost:8000 or WebAuthn endpoints
Solution:
# Check if Docker stack is running
cd web-client/docker && docker compose ps
# Start services if not running
docker compose up -d
# Check service logs if issues persist
docker compose logs webauthn-server envoy-gatewayPrevention: Always run docker compose up -d BEFORE npm start
Port Already in Use
Issue: EADDRINUSE or "port already allocated" errors
Solution: Use custom ports when generating client:
npx -y @vmenon25/mcp-server-webauthn-client \
--postgres-host-port 5433 \
--redis-host-port 6380 \
--gateway-host-port 8001 \
--forward-port 3000 \
--jaeger-ui-port 16687Quick Fix: Kill process using the port:
lsof -ti:8000 | xargs kill -9JWT Verification Failed (401 Unauthorized)
Issue: Protected endpoints return 401 Unauthorized even with JWT
Solutions:
Check JWKS endpoint is accessible:
curl http://localhost:8000/.well-known/jwks.jsonVerify JWT is included in Authorization header:
fetch('http://localhost:8000/api/example/data', { headers: { 'Authorization': `Bearer ${jwtToken}` } })Restart Envoy to clear JWKS cache:
cd docker && docker compose restart envoy-gateway
Generation Fails
Issue: Tool reports error during generation
Common Causes:
- Directory already exists at
project_path - Invalid framework name
- Template files missing
Solution:
- Choose different
project_pathor remove existing directory - Use 'vanilla', 'react', or 'vue' for framework
- Rebuild MCP server:
npm run build
Generated Client Won't Build
Issue: npm run build fails in generated client
Solution:
- Ensure all dependencies installed:
npm install - Check Node.js version:
node --version(need 18+) - Verify TypeScript version:
npx tsc --version
Contributing
Contributions welcome! Please open an issue or pull request on the GitHub repository.
License
Apache-2.0
Links
- Main Project: mpo-api-authn-server
- WebAuthn Client Library: @vmenon25/mpo-webauthn-client
- Model Context Protocol: modelcontextprotocol.io
