A production-ready OAuth-protected MCP server for Claude and other MCP clients.
Built with ktor-server-mcp and ktor-server-oauth.
This is a remote MCP server (HTTP/SSE transport, not stdio). It works out of the box with any MCP client that supports network-based servers:
- Open registration - clients register automatically via OAuth 2.0 Dynamic Client Registration (public clients with PKCE)
- Reverse proxy ready - designed to work behind nginx, Caddy, Traefik, etc. without modification
Once deployed with HTTPS (e.g., at https://mcp.example.com), add these connectors:
Claude Desktop / Web / Mobile (Pro, Max, Team, Enterprise):
- Go to Settings → Integrations
- Click "+ Add Custom Connector"
- Add each endpoint:
- Name:
Contact Sample→ URL:https://mcp.example.com/contact - Name:
Mom's Name Sample→ URL:https://mcp.example.com/mom
- Name:
- OAuth login prompts automatically on first use
Claude Code CLI:
claude mcp add --transport http contact https://mcp.example.com/contact
claude mcp add --transport http mom https://mcp.example.com/momThen use /mcp inside Claude Code to authenticate.
MCP clients like Claude require HTTPS. Options:
- Reverse proxy (recommended) - terminate SSL at nginx/Caddy/Traefik, proxy to this server on HTTP
- Ktor SSL - configure directly in
application.conf:ktor { deployment { sslPort = 8443 ssl { keyStore = /path/to/keystore.jks keyAlias = mykey keyStorePassword = changeit privateKeyPassword = changeit } } }
Both options start the server at http://localhost:8080 and work identically behind a reverse proxy with SSL for production use with Claude.
./gradlew rundocker compose -f deployment/docker-compose.yml upSee deployment/README.md for more deployment options.
- OAuth 2.0 with dynamic client registration (RFC 7591)
- Provision flow for collecting user credentials during auth
- Session-bound storage tied to OAuth tokens
- MCP tools with access to user session data
- MCP client connects and triggers OAuth flow
- User completes the provision form (e.g., enters their name/contact info)
- Data is stored in a session bound to the OAuth token (or as encrypted JWT claims)
- MCP tools access session data via
call.sessions.get<T>()or JWT claims
This sample includes two MCP endpoints demonstrating different patterns:
Uses bearer-bound sessions to store contact information.
Tools:
greet_me- Returns a personalized greetingmy_contact_info- Returns stored contact info (text or JSON)update_contact- Update email, phone, or companysend_test_email- Simulates sending email to stored address
Stores data as encrypted claims in the JWT token itself.
Tools:
your_moms_name- Returns the name stored in encrypted JWT claim
- Edit
Application.ktto add your own tools and endpoints - Modify templates in
src/main/resources/templates/for provision forms - Choose between session storage or encrypted JWT claims based on your needs
src/main/kotlin/com/example/
└── Application.kt # Server, routes, and tools
src/main/resources/templates/
├── provision.html # Contact info provision form
└── mom-provision.html # Mom's name provision form
Apache 2.0