Skip to content

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.

License

Notifications You must be signed in to change notification settings

vctrl/ktor-server-mcp

Repository files navigation

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.sessions and call.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 MCP ServerSession for SDK methods
  • call - Ktor ApplicationCall

License

Apache 2.0

About

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.

Topics

Resources

License

Stars

Watchers

Forks

Languages