Ktor Server MCP
Session-aware MCP server integration for Ktor. A thin wrapper around the official MCP Kotlin SDK, designed to work with ktor-server-oauth and any authenticate {} flow.
Ask AI about Ktor Server MCP
Powered by Claude · Grounded in docs
I know everything about Ktor Server MCP. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
ktor-server-mcp
Session-aware MCP server integration for Ktor.
A thin wrapper around the official MCP Kotlin SDK, designed to work with ktor-server-oauth and any Ktor authenticate {} flow. Build MCP servers where tools have access to authenticated user sessions and principals.
Why This Library?
The official MCP Kotlin SDK provides excellent protocol support but doesn't integrate with Ktor sessions or authentication blocks. This library bridges that gap:
- Works inside
authenticate {}- Respects Ktor's route hierarchy, so MCP endpoints can be protected by any auth provider. - Session & Principal access - Read user-specific data via
call.sessionsandcall.principal<T>(). - Designed for ktor-server-oauth - Seamlessly integrates with OAuth provision flows.
- Idiomatic Kotlin DSL - Clean
tool()syntax with automatic error handling. - Full MCP Kotlin SDK passthrough - Use
configure {}for prompts, resources, and all official SDK features.
Installation
This library was built to bring MCP support to ktor-server-oauth, enabling OAuth-protected MCP servers with session-aware tools.
dependencies {
implementation("com.vcontrol:ktor-server-mcp:0.3.0")
implementation("io.modelcontextprotocol:kotlin-sdk:0.8.1")
// Recommended: OAuth 2.0 support
implementation("com.vcontrol:ktor-server-oauth:0.5.0")
}
Quick Start
fun Application.module() {
install(SSE)
install(Authentication) {
bearer("api-key") { /* ... */ }
}
routing {
authenticate("api-key") {
mcp("/mcp") {
name = "my-server"
version = "1.0.0"
tool("greet", "Greets the user") {
val name = args["name"] ?: "World"
textResult("Hello, $name!")
}
}
}
}
}
Session & Principal Access
Access authenticated user data from your tools:
@Serializable
data class UserSession(val apiKey: String, val name: String)
routing {
authenticate {
mcp("/mcp") {
val user = call.sessions.get<UserSession>()
val principal = call.principal<UserPrincipal>()
tool("whoami", "Returns current user") {
textResult("Hello, ${user?.name ?: principal?.name ?: "stranger"}!")
}
tool("call_api", "Calls API with user's key") {
val endpoint = args["endpoint"] ?: "/default"
val result = apiClient.call(endpoint, user?.apiKey)
textResult(result)
}
}
}
}
Helper Functions
Simple helpers for common response patterns:
// Single text result
textResult("Hello!")
// Multiple text results
textResult("Line 1", "Line 2", "Line 3")
// Error result
errorResult("Something went wrong")
Error Handling
Exceptions are automatically caught and returned as error results:
tool("risky", "Might fail") {
if (args["value"] == null) {
throw IllegalArgumentException("value is required")
}
textResult("Success!")
}
// Errors returned as: CallToolResult(isError = true, content = "Error: value is required")
With ktor-server-oauth
Pair with ktor-server-oauth for OAuth 2.0 protected MCP servers:
fun Application.module() {
install(OAuth) {
server { clients { registration = true } } // Accept all registrations
}
install(OAuthSessions) {
session<ApiKeySession>()
}
install(Authentication) {
oauthJwt()
}
routing {
provision {
get { call.respondHtml { apiKeyForm() } }
post {
call.sessions.set(ApiKeySession(call.receiveParameters()["api_key"]!!))
call.provision.complete()
}
}
authenticate {
mcp("/mcp") {
val session = call.sessions.get<ApiKeySession>()
tool("query", "Query using user's API key") {
val result = queryWithKey(session?.apiKey)
textResult(result)
}
}
}
}
}
See ktor-oauth-mcp-samples for complete working examples.
SDK Passthrough
Use configure {} for full SDK access (prompts, resources, advanced features):
mcp("/mcp") {
tool("hello", "Says hello") {
textResult("Hello!")
}
configure {
val user = call.sessions.get<UserSession>()
server.addPrompt("summarize", "Summarizes text") { request ->
GetPromptResult(messages = listOf(...))
}
server.addResource("config://settings", "User settings") { request ->
ReadResourceResult(contents = listOf(...))
}
}
}
API Reference
Route.mcp()
fun Route.mcp(path: String = "", configure: McpConfig.() -> Unit): Route
Registers MCP SSE and POST endpoints at the given path.
McpConfig
| Property | Description |
|---|---|
name | Server name (shown to clients) |
version | Server version |
title | Human-readable title (optional) |
websiteUrl | Server website URL (optional) |
icons | Server icons (optional) |
capabilities | ServerCapabilities - auto-set when using tool() |
call | Ktor ApplicationCall for sessions, principal, etc. |
tool()
fun tool(
name: String,
description: String,
inputSchema: ToolSchema = ToolSchema(),
handler: suspend ToolScope.() -> CallToolResult
)
Register a tool. Use textResult() helper for simple responses. Errors are caught automatically.
ToolScope
| Property | Description |
|---|---|
args | Tool arguments as Map<String, Any?> |
call | Ktor ApplicationCall for sessions, principal, etc. |
Helper Functions
fun textResult(text: String): CallToolResult
fun textResult(vararg texts: String): CallToolResult
fun errorResult(message: String): CallToolResult
configure {}
fun configure(block: suspend ConfigureScope.() -> Unit)
Direct SDK access. ConfigureScope provides:
server- the MCPServerSessionfor SDK methodscall- KtorApplicationCall
License
Apache 2.0
