Add ktlint (#213)

* Add ktlint

* Simplify `initClient` function and clean imports in SseIntegrationTest.
This commit is contained in:
Pavel Gorgulov 2025-08-05 15:10:44 +02:00 • committed by GitHub
commit 91cf1205af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 800 additions and 758 deletions

View file

@ -25,4 +25,4 @@ kotlin {
}
}
}
}
}

View file

@ -94,9 +94,7 @@ internal fun ServerSSESession.mcpSseTransport(
return transport
}
internal suspend fun RoutingContext.mcpPostEndpoint(
transports: ConcurrentMap<String, SseServerTransport>,
) {
internal suspend fun RoutingContext.mcpPostEndpoint(transports: ConcurrentMap<String, SseServerTransport>) {
val sessionId: String = call.request.queryParameters["sessionId"]
?: run {
call.respond(HttpStatusCode.BadRequest, "sessionId query parameter is not provided")

View file

@ -28,10 +28,8 @@ public typealias SSEServerTransport = SseServerTransport
* Creates a new SSE server transport, which will direct the client to POST messages to the relative or absolute URL identified by `_endpoint`.
*/
@OptIn(ExperimentalAtomicApi::class)
public class SseServerTransport(
private val endpoint: String,
private val session: ServerSSESession,
) : AbstractTransport() {
public class SseServerTransport(private val endpoint: String, private val session: ServerSSESession) :
AbstractTransport() {
private val initialized: AtomicBoolean = AtomicBoolean(false)
@OptIn(ExperimentalUuidApi::class)
@ -44,13 +42,15 @@ public class SseServerTransport(
*/
override suspend fun start() {
if (!initialized.compareAndSet(expectedValue = false, newValue = true)) {
error("SSEServerTransport already started! If using Server class, note that connect() calls start() automatically.")
error(
"SSEServerTransport already started! If using Server class, note that connect() calls start() automatically.",
)
}
// Send the endpoint event
session.send(
event = "endpoint",
data = "${endpoint.encodeURLPath()}?$SESSION_ID_PARAM=${sessionId}",
data = "${endpoint.encodeURLPath()}?$SESSION_ID_PARAM=$sessionId",
)
try {

View file

@ -64,10 +64,8 @@ private val logger = KotlinLogging.logger {}
* @property capabilities The capabilities this server supports.
* @property enforceStrictCapabilities Whether to strictly enforce capabilities when interacting with clients.
*/
public class ServerOptions(
public val capabilities: ServerCapabilities,
enforceStrictCapabilities: Boolean = true,
) : ProtocolOptions(enforceStrictCapabilities = enforceStrictCapabilities)
public class ServerOptions(public val capabilities: ServerCapabilities, enforceStrictCapabilities: Boolean = true) :
ProtocolOptions(enforceStrictCapabilities = enforceStrictCapabilities)
/**
* An MCP server on top of a pluggable transport.
@ -79,11 +77,11 @@ public class ServerOptions(
* @param serverInfo Information about this server implementation (name, version).
* @param options Configuration options for the server.
*/
public open class Server(
private val serverInfo: Implementation,
options: ServerOptions,
) : Protocol(options) {
public open class Server(private val serverInfo: Implementation, options: ServerOptions) : Protocol(options) {
@Suppress("ktlint:standard:backing-property-naming")
private var _onInitialized: (() -> Unit) = {}
@Suppress("ktlint:standard:backing-property-naming")
private var _onClose: () -> Unit = {}
/**
@ -221,7 +219,7 @@ public open class Server(
title: String? = null,
outputSchema: Tool.Output? = null,
toolAnnotations: ToolAnnotations? = null,
handler: suspend (CallToolRequest) -> CallToolResult
handler: suspend (CallToolRequest) -> CallToolResult,
) {
val tool = Tool(name, title, description, inputSchema, outputSchema, toolAnnotations)
addTool(tool, handler)
@ -325,7 +323,7 @@ public open class Server(
name: String,
description: String? = null,
arguments: List<PromptArgument>? = null,
promptProvider: suspend (GetPromptRequest) -> GetPromptResult
promptProvider: suspend (GetPromptRequest) -> GetPromptResult,
) {
val prompt = Prompt(name = name, description = description, arguments = arguments)
addPrompt(prompt, promptProvider)
@ -416,7 +414,7 @@ public open class Server(
name: String,
description: String,
mimeType: String = "text/html",
readHandler: suspend (ReadResourceRequest) -> ReadResourceResult
readHandler: suspend (ReadResourceRequest) -> ReadResourceResult,
) {
if (capabilities.resources == null) {
logger.error { "Failed to add resource '$name': Server does not support resources capability" }
@ -426,7 +424,7 @@ public open class Server(
_resources.update { current ->
current.put(
uri,
RegisteredResource(Resource(uri, name, description, mimeType), readHandler)
RegisteredResource(Resource(uri, name, description, mimeType), readHandler),
)
}
}
@ -507,9 +505,7 @@ public open class Server(
* @return The result of the ping request.
* @throws IllegalStateException If for some reason the method is not supported or the connection is closed.
*/
public suspend fun ping(): EmptyRequestResult {
return request<EmptyRequestResult>(PingRequest())
}
public suspend fun ping(): EmptyRequestResult = request<EmptyRequestResult>(PingRequest())
/**
* Creates a message using the server's sampling capability.
@ -521,7 +517,7 @@ public open class Server(
*/
public suspend fun createMessage(
params: CreateMessageRequest,
options: RequestOptions? = null
options: RequestOptions? = null,
): CreateMessageResult {
logger.debug { "Creating message with params: $params" }
return request<CreateMessageResult>(params, options)
@ -537,7 +533,7 @@ public open class Server(
*/
public suspend fun listRoots(
params: JsonObject = EmptyJsonObject,
options: RequestOptions? = null
options: RequestOptions? = null,
): ListRootsResult {
logger.debug { "Listing roots with params: $params" }
return request<ListRootsResult>(ListRootsRequest(params), options)
@ -546,7 +542,7 @@ public open class Server(
public suspend fun createElicitation(
message: String,
requestedSchema: RequestedSchema,
options: RequestOptions? = null
options: RequestOptions? = null,
): CreateElicitationResult {
logger.debug { "Creating elicitation with message: $message" }
return request(CreateElicitationRequest(message, requestedSchema), options)
@ -607,14 +603,16 @@ public open class Server(
val protocolVersion = if (SUPPORTED_PROTOCOL_VERSIONS.contains(requestedVersion)) {
requestedVersion
} else {
logger.warn { "Client requested unsupported protocol version $requestedVersion, falling back to $LATEST_PROTOCOL_VERSION" }
logger.warn {
"Client requested unsupported protocol version $requestedVersion, falling back to $LATEST_PROTOCOL_VERSION"
}
LATEST_PROTOCOL_VERSION
}
return InitializeResult(
protocolVersion = protocolVersion,
capabilities = capabilities,
serverInfo = serverInfo
serverInfo = serverInfo,
)
}
@ -723,26 +721,34 @@ public open class Server(
}
"notifications/resources/updated",
"notifications/resources/list_changed" -> {
"notifications/resources/list_changed",
-> {
if (capabilities.resources == null) {
throw IllegalStateException("Server does not support notifying about resources (required for ${method.value})")
throw IllegalStateException(
"Server does not support notifying about resources (required for ${method.value})",
)
}
}
"notifications/tools/list_changed" -> {
if (capabilities.tools == null) {
throw IllegalStateException("Server does not support notifying of tool list changes (required for ${method.value})")
throw IllegalStateException(
"Server does not support notifying of tool list changes (required for ${method.value})",
)
}
}
"notifications/prompts/list_changed" -> {
if (capabilities.prompts == null) {
throw IllegalStateException("Server does not support notifying of prompt list changes (required for ${method.value})")
throw IllegalStateException(
"Server does not support notifying of prompt list changes (required for ${method.value})",
)
}
}
"notifications/cancelled",
"notifications/progress" -> {
"notifications/progress",
-> {
// Always allowed
}
}
@ -772,7 +778,8 @@ public open class Server(
}
"prompts/get",
"prompts/list" -> {
"prompts/list",
-> {
if (capabilities.prompts == null) {
throw IllegalStateException("Server does not support prompts (required for $method)")
}
@ -780,14 +787,16 @@ public open class Server(
"resources/list",
"resources/templates/list",
"resources/read" -> {
"resources/read",
-> {
if (capabilities.resources == null) {
throw IllegalStateException("Server does not support resources (required for $method)")
}
}
"tools/call",
"tools/list" -> {
"tools/list",
-> {
if (capabilities.tools == null) {
throw IllegalStateException("Server does not support tools (required for $method)")
}
@ -806,10 +815,7 @@ public open class Server(
* @property tool The tool definition.
* @property handler A suspend function to handle the tool call requests.
*/
public data class RegisteredTool(
val tool: Tool,
val handler: suspend (CallToolRequest) -> CallToolResult
)
public data class RegisteredTool(val tool: Tool, val handler: suspend (CallToolRequest) -> CallToolResult)
/**
* A wrapper class representing a registered prompt on the server.
@ -819,7 +825,7 @@ public data class RegisteredTool(
*/
public data class RegisteredPrompt(
val prompt: Prompt,
val messageProvider: suspend (GetPromptRequest) -> GetPromptResult
val messageProvider: suspend (GetPromptRequest) -> GetPromptResult,
)
/**
@ -830,5 +836,5 @@ public data class RegisteredPrompt(
*/
public data class RegisteredResource(
val resource: Resource,
val readHandler: suspend (ReadResourceRequest) -> ReadResourceResult
val readHandler: suspend (ReadResourceRequest) -> ReadResourceResult,
)

View file

@ -31,10 +31,7 @@ import kotlin.coroutines.CoroutineContext
* Reads from System.in and writes to System.out.
*/
@OptIn(ExperimentalAtomicApi::class)
public class StdioServerTransport(
private val inputStream: Source,
outputStream: Sink
) : AbstractTransport() {
public class StdioServerTransport(private val inputStream: Source, outputStream: Sink) : AbstractTransport() {
private val logger = KotlinLogging.logger {}
private val readBuffer = ReadBuffer()

View file

@ -14,10 +14,7 @@ import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME
* @param options Optional server configuration settings for the MCP server.
* @param handler A suspend function that defines the server's behavior.
*/
public fun Route.mcpWebSocket(
options: ServerOptions? = null,
handler: suspend Server.() -> Unit = {},
) {
public fun Route.mcpWebSocket(options: ServerOptions? = null, handler: suspend Server.() -> Unit = {}) {
webSocket {
createMcpServer(this, options, handler)
}
@ -30,11 +27,7 @@ public fun Route.mcpWebSocket(
* @param options Optional server configuration settings for the MCP server.
* @param handler A suspend function that defines the server's behavior.
*/
public fun Route.mcpWebSocket(
path: String,
options: ServerOptions? = null,
handler: suspend Server.() -> Unit = {},
) {
public fun Route.mcpWebSocket(path: String, options: ServerOptions? = null, handler: suspend Server.() -> Unit = {}) {
webSocket(path) {
createMcpServer(this, options, handler)
}
@ -45,9 +38,7 @@ public fun Route.mcpWebSocket(
*
* @param handler A suspend function that defines the behavior of the transport layer.
*/
public fun Route.mcpWebSocketTransport(
handler: suspend WebSocketMcpServerTransport.() -> Unit = {},
) {
public fun Route.mcpWebSocketTransport(handler: suspend WebSocketMcpServerTransport.() -> Unit = {}) {
webSocket {
val transport = createMcpTransport(this)
transport.start()
@ -62,10 +53,7 @@ public fun Route.mcpWebSocketTransport(
* @param path The URL path at which to register the WebSocket route.
* @param handler A suspend function that defines the behavior of the transport layer.
*/
public fun Route.mcpWebSocketTransport(
path: String,
handler: suspend WebSocketMcpServerTransport.() -> Unit = {},
) {
public fun Route.mcpWebSocketTransport(path: String, handler: suspend WebSocketMcpServerTransport.() -> Unit = {}) {
webSocket(path) {
val transport = createMcpTransport(this)
transport.start()
@ -74,7 +62,6 @@ public fun Route.mcpWebSocketTransport(
}
}
private suspend fun Route.createMcpServer(
session: WebSocketServerSession,
options: ServerOptions?,
@ -85,14 +72,14 @@ private suspend fun Route.createMcpServer(
val server = Server(
serverInfo = Implementation(
name = IMPLEMENTATION_NAME,
version = LIB_VERSION
version = LIB_VERSION,
),
options = options ?: ServerOptions(
capabilities = ServerCapabilities(
prompts = ServerCapabilities.Prompts(listChanged = null),
resources = ServerCapabilities.Resources(subscribe = null, listChanged = null),
tools = ServerCapabilities.Tools(listChanged = null),
)
),
),
)
@ -101,8 +88,5 @@ private suspend fun Route.createMcpServer(
server.close()
}
private fun createMcpTransport(
session: WebSocketServerSession,
): WebSocketMcpServerTransport {
return WebSocketMcpServerTransport(session)
}
private fun createMcpTransport(session: WebSocketServerSession): WebSocketMcpServerTransport =
WebSocketMcpServerTransport(session)

View file

@ -10,9 +10,7 @@ import io.modelcontextprotocol.kotlin.sdk.shared.WebSocketMcpTransport
*
* @property session The WebSocket server session used for communication.
*/
public class WebSocketMcpServerTransport(
override val session: WebSocketServerSession,
) : WebSocketMcpTransport() {
public class WebSocketMcpServerTransport(override val session: WebSocketServerSession) : WebSocketMcpTransport() {
override suspend fun initializeSession() {
val subprotocol = session.call.request.headers[HttpHeaders.SecWebSocketProtocol]
if (subprotocol != MCP_SUBPROTOCOL) {