Enable Explicit Api (#21)

This commit is contained in:
Alexander Sysoev 2024-12-16 20:59:53 +01:00 • committed by GitHub
commit 852a4a3d5e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 291 additions and 272 deletions

1
.idea/vcs.xml generated
View file

@ -2,6 +2,5 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/samples/kotlin-mcp-server" vcs="Git" />
</component>
</project>

View file

@ -1,4 +1,5 @@
import org.jetbrains.dokka.gradle.engine.parameters.VisibilityModifier
import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
plugins {
alias(libs.plugins.kotlin.jvm)
@ -207,6 +208,8 @@ val generateLibVersionTask =
tasks.register<GenerateLibVersionTask>("generateLibVersion", version.toString(), sourcesDir)
kotlin {
explicitApi = ExplicitApiMode.Strict
jvmToolchain(21)
sourceSets {

View file

@ -17,8 +17,8 @@ import org.jetbrains.kotlinx.mcp.shared.Transport
* @property capabilities The capabilities this client supports.
* @property enforceStrictCapabilities Whether to strictly enforce capabilities when interacting with the server.
*/
class ClientOptions(
val capabilities: ClientCapabilities = ClientCapabilities(),
public class ClientOptions(
public val capabilities: ClientCapabilities = ClientCapabilities(),
enforceStrictCapabilities: Boolean = true,
) : ProtocolOptions(enforceStrictCapabilities = enforceStrictCapabilities)
@ -33,7 +33,7 @@ class ClientOptions(
* @param clientInfo Information about the client implementation (name, version).
* @param options Configuration options for this client.
*/
open class Client(
public open class Client(
private val clientInfo: Implementation,
options: ClientOptions = ClientOptions(),
) : Protocol<ClientRequest, ClientNotification, ClientResult>(options) {
@ -95,7 +95,7 @@ open class Client(
*
* @return The server's capabilities, or `null` if initialization is not yet complete.
*/
fun getServerCapabilities(): ServerCapabilities? {
public fun getServerCapabilities(): ServerCapabilities? {
return serverCapabilities
}
@ -104,7 +104,7 @@ open class Client(
*
* @return Information about the server's implementation, or `null` if initialization is not yet complete.
*/
fun getServerVersion(): Implementation? {
public fun getServerVersion(): Implementation? {
return serverVersion
}
@ -219,7 +219,7 @@ open class Client(
* @param options Optional request options.
* @throws IllegalStateException If the server does not support the ping method (unlikely).
*/
suspend fun ping(options: RequestOptions? = null): EmptyRequestResult {
public suspend fun ping(options: RequestOptions? = null): EmptyRequestResult {
return request<EmptyRequestResult>(PingRequest(), options)
}
@ -231,7 +231,7 @@ open class Client(
* @return The completion result returned by the server, or `null` if none.
* @throws IllegalStateException If the server does not support prompts or completion.
*/
suspend fun complete(params: CompleteRequest, options: RequestOptions? = null): CompleteResult? {
public suspend fun complete(params: CompleteRequest, options: RequestOptions? = null): CompleteResult? {
return request<CompleteResult>(params, options)
}
@ -242,7 +242,7 @@ open class Client(
* @param options Optional request options.
* @throws IllegalStateException If the server does not support logging.
*/
suspend fun setLoggingLevel(level: LoggingLevel, options: RequestOptions? = null): EmptyRequestResult {
public suspend fun setLoggingLevel(level: LoggingLevel, options: RequestOptions? = null): EmptyRequestResult {
return request<EmptyRequestResult>(SetLevelRequest(level), options)
}
@ -254,7 +254,7 @@ open class Client(
* @return The requested prompt details, or `null` if not found.
* @throws IllegalStateException If the server does not support prompts.
*/
suspend fun getPrompt(request: GetPromptRequest, options: RequestOptions? = null): GetPromptResult? {
public suspend fun getPrompt(request: GetPromptRequest, options: RequestOptions? = null): GetPromptResult? {
return request<GetPromptResult>(request, options)
}
@ -266,7 +266,7 @@ open class Client(
* @return The list of available prompts, or `null` if none.
* @throws IllegalStateException If the server does not support prompts.
*/
suspend fun listPrompts(
public suspend fun listPrompts(
request: ListPromptsRequest = ListPromptsRequest(),
options: RequestOptions? = null,
): ListPromptsResult? {
@ -281,7 +281,7 @@ open class Client(
* @return The list of resources, or `null` if none.
* @throws IllegalStateException If the server does not support resources.
*/
suspend fun listResources(
public suspend fun listResources(
request: ListResourcesRequest = ListResourcesRequest(),
options: RequestOptions? = null,
): ListResourcesResult? {
@ -296,7 +296,7 @@ open class Client(
* @return The list of resource templates, or `null` if none.
* @throws IllegalStateException If the server does not support resources.
*/
suspend fun listResourceTemplates(
public suspend fun listResourceTemplates(
request: ListResourceTemplatesRequest,
options: RequestOptions? = null,
): ListResourceTemplatesResult? {
@ -311,7 +311,10 @@ open class Client(
* @return The resource content, or `null` if the resource is not found.
* @throws IllegalStateException If the server does not support resources.
*/
suspend fun readResource(request: ReadResourceRequest, options: RequestOptions? = null): ReadResourceResult? {
public suspend fun readResource(
request: ReadResourceRequest,
options: RequestOptions? = null,
): ReadResourceResult? {
return request<ReadResourceResult>(request, options)
}
@ -322,7 +325,10 @@ open class Client(
* @param options Optional request options.
* @throws IllegalStateException If the server does not support resource subscriptions.
*/
suspend fun subscribeResource(request: SubscribeRequest, options: RequestOptions? = null): EmptyRequestResult {
public suspend fun subscribeResource(
request: SubscribeRequest,
options: RequestOptions? = null,
): EmptyRequestResult {
return request<EmptyRequestResult>(request, options)
}
@ -333,7 +339,10 @@ open class Client(
* @param options Optional request options.
* @throws IllegalStateException If the server does not support resource subscriptions.
*/
suspend fun unsubscribeResource(request: UnsubscribeRequest, options: RequestOptions? = null): EmptyRequestResult {
public suspend fun unsubscribeResource(
request: UnsubscribeRequest,
options: RequestOptions? = null,
): EmptyRequestResult {
return request<EmptyRequestResult>(request, options)
}
@ -347,7 +356,7 @@ open class Client(
* @return The result of the tool call, or `null` if none.
* @throws IllegalStateException If the server does not support tools.
*/
suspend fun callTool(
public suspend fun callTool(
name: String,
arguments: Map<String, Any?>,
compatibility: Boolean = false,
@ -380,7 +389,7 @@ open class Client(
* @return The result of the tool call, or `null` if none.
* @throws IllegalStateException If the server does not support tools.
*/
suspend fun callTool(
public suspend fun callTool(
request: CallToolRequest,
compatibility: Boolean = false,
options: RequestOptions? = null,
@ -400,7 +409,7 @@ open class Client(
* @return The list of available tools, or `null` if none.
* @throws IllegalStateException If the server does not support tools.
*/
suspend fun listTools(
public suspend fun listTools(
request: ListToolsRequest = ListToolsRequest(),
options: RequestOptions? = null,
): ListToolsResult? {
@ -413,7 +422,7 @@ open class Client(
*
* @throws IllegalStateException If the client or server does not support roots.
*/
suspend fun sendRootsListChanged() {
public suspend fun sendRootsListChanged() {
notification(RootsListChangedNotification())
}
}
}

View file

@ -18,7 +18,7 @@ import kotlin.time.Duration
* Client transport for SSE: this will connect to a server using Server-Sent Events for receiving
* messages and make separate POST requests for sending messages.
*/
class SSEClientTransport(
public class SSEClientTransport(
private val client: HttpClient,
private val urlString: String?,
private val reconnectionTime: Duration? = null,

View file

@ -23,7 +23,7 @@ import kotlin.text.Charsets.UTF_8
* @param input The input stream where messages are received.
* @param output The output stream where messages are sent.
*/
class StdioClientTransport(
public class StdioClientTransport(
private val input: InputStream,
private val output: OutputStream
) : Transport {
@ -128,4 +128,4 @@ class StdioClientTransport(
}
}
}
}
}

View file

@ -12,7 +12,7 @@ import kotlin.properties.Delegates
/**
* Client transport for WebSocket: this will connect to a server over the WebSocket protocol.
*/
class WebSocketClientTransport(
public class WebSocketClientTransport(
private val client: HttpClient,
private val urlString: String?,
private val requestBuilder: HttpRequestBuilder.() -> Unit = {},

View file

@ -13,7 +13,7 @@ import org.jetbrains.kotlinx.mcp.shared.IMPLEMENTATION_NAME
* @param requestBuilder Optional lambda to configure the HTTP request.
* @return A [WebSocketClientTransport] configured for MCP communication.
*/
fun HttpClient.mcpWebSocketTransport(
public fun HttpClient.mcpWebSocketTransport(
urlString: String? = null,
requestBuilder: HttpRequestBuilder.() -> Unit = {},
): WebSocketClientTransport = WebSocketClientTransport(this, urlString, requestBuilder)
@ -25,7 +25,7 @@ fun HttpClient.mcpWebSocketTransport(
* @param requestBuilder Optional lambda to configure the HTTP request.
* @return A connected [Client] ready for MCP communication.
*/
suspend fun HttpClient.mcpWebSocket(
public suspend fun HttpClient.mcpWebSocket(
urlString: String? = null,
requestBuilder: HttpRequestBuilder.() -> Unit = {},
): Client {

View file

@ -15,7 +15,7 @@ import kotlin.time.Duration
* @param requestBuilder Optional lambda to configure the HTTP request.
* @return A [SSEClientTransport] configured for MCP communication.
*/
fun HttpClient.mcpSseTransport(
public fun HttpClient.mcpSseTransport(
urlString: String? = null,
reconnectionTime: Duration? = null,
requestBuilder: HttpRequestBuilder.() -> Unit = {},
@ -29,7 +29,7 @@ fun HttpClient.mcpSseTransport(
* @param requestBuilder Optional lambda to configure the HTTP request.
* @return A connected [Client] ready for MCP communication.
*/
suspend fun HttpClient.mcpSse(
public suspend fun HttpClient.mcpSse(
urlString: String? = null,
reconnectionTime: Duration? = null,
requestBuilder: HttpRequestBuilder.() -> Unit = {},

View file

@ -13,7 +13,7 @@ private val logger = KotlinLogging.logger {}
/**
* Configures the Ktor Application to handle Model Context Protocol (MCP) over Server-Sent Events (SSE).
*/
fun Application.MCP(block: () -> Server) {
public fun Application.MCP(block: () -> Server) {
val servers = ConcurrentMap<String, Server>()
install(SSE)
@ -51,4 +51,4 @@ fun Application.MCP(block: () -> Server) {
logger.trace { "Message handled for sessionId: $sessionId" }
}
}
}
}

View file

@ -21,14 +21,14 @@ internal const val SESSION_ID_PARAM = "sessionId"
*
* Creates a new SSE server transport, which will direct the client to POST messages to the relative or absolute URL identified by `_endpoint`.
*/
class SSEServerTransport(
public class SSEServerTransport(
private val endpoint: String,
private val session: ServerSSESession,
) : Transport {
private val initialized = AtomicBoolean(false)
@OptIn(ExperimentalUuidApi::class)
val sessionId: String = Uuid.random().toString()
public val sessionId: String = Uuid.random().toString()
override var onClose: (() -> Unit)? = null
override var onError: ((Throwable) -> Unit)? = null
@ -62,7 +62,7 @@ class SSEServerTransport(
*
* This should be called when a POST request is made to send a message to the server.
*/
suspend fun handlePostMessage(call: ApplicationCall) {
public suspend fun handlePostMessage(call: ApplicationCall) {
if (!initialized.get()) {
val message = "SSE connection not established"
call.respondText(message, status = HttpStatusCode.InternalServerError)
@ -96,7 +96,7 @@ class SSEServerTransport(
* Handle a client message, regardless of how it arrived.
* This can be used to inform the server of messages that arrive via a means different from HTTP POST.
*/
suspend fun handleMessage(message: String) {
public suspend fun handleMessage(message: String) {
try {
val parsedMessage = McpJson.decodeFromString<JSONRPCMessage>(message)
onMessage?.invoke(parsedMessage)

View file

@ -16,8 +16,8 @@ private val logger = KotlinLogging.logger {}
* @property capabilities The capabilities this server supports.
* @property enforceStrictCapabilities Whether to strictly enforce capabilities when interacting with clients.
*/
class ServerOptions(
val capabilities: ServerCapabilities,
public class ServerOptions(
public val capabilities: ServerCapabilities,
enforceStrictCapabilities: Boolean = true,
) : ProtocolOptions(enforceStrictCapabilities = enforceStrictCapabilities)
@ -568,4 +568,4 @@ data class RegisteredPrompt(
data class RegisteredResource(
val resource: Resource,
val readHandler: suspend (ReadResourceRequest) -> ReadResourceResult
)
)

View file

@ -17,9 +17,9 @@ import kotlin.coroutines.CoroutineContext
*
* Reads from System.in and writes to System.out.
*/
class StdioServerTransport(
public class StdioServerTransport(
private val inputStream: BufferedInputStream = BufferedInputStream(System.`in`),
private val outputStream: PrintStream = System.out
outputStream: PrintStream = System.out
) : Transport {
private val logger = KotlinLogging.logger {}
override var onClose: (() -> Unit)? = null
@ -115,4 +115,4 @@ class StdioServerTransport(
outputWriter.flush()
}
}
}
}

View file

@ -13,7 +13,7 @@ import org.jetbrains.kotlinx.mcp.shared.IMPLEMENTATION_NAME
* @param options Optional server configuration settings for the MCP server.
* @param handler A suspend function that defines the server's behavior.
*/
fun Route.mcpWebSocket(
public fun Route.mcpWebSocket(
options: ServerOptions? = null,
handler: suspend Server.() -> Unit = {},
) {
@ -29,7 +29,7 @@ fun Route.mcpWebSocket(
* @param options Optional server configuration settings for the MCP server.
* @param handler A suspend function that defines the server's behavior.
*/
fun Route.mcpWebSocket(
public fun Route.mcpWebSocket(
path: String,
options: ServerOptions? = null,
handler: suspend Server.() -> Unit = {},
@ -44,7 +44,7 @@ fun Route.mcpWebSocket(
*
* @param handler A suspend function that defines the behavior of the transport layer.
*/
fun Route.mcpWebSocketTransport(
public fun Route.mcpWebSocketTransport(
handler: suspend WebSocketMcpServerTransport.() -> Unit = {},
) {
webSocket {
@ -61,7 +61,7 @@ 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.
*/
fun Route.mcpWebSocketTransport(
public fun Route.mcpWebSocketTransport(
path: String,
handler: suspend WebSocketMcpServerTransport.() -> Unit = {},
) {

View file

@ -10,7 +10,7 @@ import org.jetbrains.kotlinx.mcp.shared.WebSocketMcpTransport
*
* @property session The WebSocket server session used for communication.
*/
class WebSocketMcpServerTransport(
public class WebSocketMcpServerTransport(
override val session: WebSocketServerSession,
) : WebSocketMcpTransport() {
override suspend fun initializeSession() {

View file

@ -27,6 +27,7 @@ import kotlinx.serialization.serializer
import org.jetbrains.kotlinx.mcp.fromJSON
import org.jetbrains.kotlinx.mcp.toJSON
import kotlin.collections.get
import kotlin.reflect.KType
import kotlin.reflect.typeOf
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
@ -38,7 +39,7 @@ internal const val IMPLEMENTATION_NAME = "mcp-ktor"
/**
* Callback for progress notifications.
*/
typealias ProgressCallback = (Progress) -> Unit
public typealias ProgressCallback = (Progress) -> Unit
@OptIn(ExperimentalSerializationApi::class)
@PublishedApi
@ -55,7 +56,7 @@ internal val McpJson: Json by lazy {
/**
* Additional initialization options.
*/
open class ProtocolOptions(
public open class ProtocolOptions(
/**
* Whether to restrict emitted requests to only those that the remote side has indicated
* that they can handle, through their advertised capabilities.
@ -66,20 +67,20 @@ open class ProtocolOptions(
* Currently this defaults to false, for backwards compatibility with SDK versions
* that did not advertise capabilities correctly. In future, this will default to true.
*/
var enforceStrictCapabilities: Boolean = false,
public var enforceStrictCapabilities: Boolean = false,
var timeout: Duration = DEFAULT_REQUEST_TIMEOUT
public var timeout: Duration = DEFAULT_REQUEST_TIMEOUT,
)
/**
* The default request timeout.
*/
val DEFAULT_REQUEST_TIMEOUT: Duration = 60000.milliseconds
public val DEFAULT_REQUEST_TIMEOUT: Duration = 60000.milliseconds
/**
* Options that can be given per request.
*/
data class RequestOptions(
public data class RequestOptions(
/**
* If set, requests progress notifications from the remote end (if supported).
* When progress notifications are received, this callback will be invoked.
@ -92,13 +93,13 @@ data class RequestOptions(
*
* If not specified, `DEFAULT_REQUEST_TIMEOUT` will be used as the timeout.
*/
val timeout: Duration = DEFAULT_REQUEST_TIMEOUT
val timeout: Duration = DEFAULT_REQUEST_TIMEOUT,
)
/**
* Extra data given to request handlers.
*/
class RequestHandlerExtra()
public class RequestHandlerExtra()
internal val COMPLETED = CompletableDeferred(Unit).also { it.complete(Unit) }
@ -106,16 +107,16 @@ internal val COMPLETED = CompletableDeferred(Unit).also { it.complete(Unit) }
* Implements MCP protocol framing on top of a pluggable transport, including
* features like request/response linking, notifications, and progress.
*/
abstract class Protocol<SendRequestT : Request, SendNotificationT : Notification, SendResultT : RequestResult>(
@PublishedApi internal val _options: ProtocolOptions?
public abstract class Protocol<SendRequestT : Request, SendNotificationT : Notification, SendResultT : RequestResult>(
@PublishedApi internal val options: ProtocolOptions?,
) {
var transport: Transport? = null
public var transport: Transport? = null
private set
@PublishedApi
internal val requestHandlers: MutableMap<String, suspend (request: JSONRPCRequest, extra: RequestHandlerExtra) -> SendResultT?> =
mutableMapOf()
val notificationHandlers: MutableMap<String, suspend (notification: JSONRPCNotification) -> Unit> =
public val notificationHandlers: MutableMap<String, suspend (notification: JSONRPCNotification) -> Unit> =
mutableMapOf()
@PublishedApi
@ -130,24 +131,25 @@ abstract class Protocol<SendRequestT : Request, SendNotificationT : Notification
*
* This is invoked when close() is called as well.
*/
open fun onclose() {}
public open fun onclose() {}
/**
* Callback for when an error occurs.
*
* Note that errors are not necessarily fatal they are used for reporting any kind of exceptional condition out of band.
*/
open fun onerror(error: Throwable) {}
public open fun onerror(error: Throwable) {}
/**
* A handler to invoke for any request types that do not have their own handler installed.
*/
var fallbackRequestHandler: (suspend (request: JSONRPCRequest, extra: RequestHandlerExtra) -> SendResultT?)? = null
public var fallbackRequestHandler: (suspend (request: JSONRPCRequest, extra: RequestHandlerExtra) -> SendResultT?)? =
null
/**
* A handler to invoke for any notification types that do not have their own handler installed.
*/
var fallbackNotificationHandler: (suspend (notification: JSONRPCNotification) -> Unit)? = null
public var fallbackNotificationHandler: (suspend (notification: JSONRPCNotification) -> Unit)? = null
init {
setNotificationHandler<ProgressNotification>(Method.Defined.NotificationsProgress) { notification ->
@ -166,7 +168,7 @@ abstract class Protocol<SendRequestT : Request, SendNotificationT : Notification
*
* The Protocol object assumes ownership of the Transport, replacing any callbacks that have already been set, and expects that it is the only user of the Transport instance going forward.
*/
open suspend fun connect(transport: Transport) {
public open suspend fun connect(transport: Transport) {
this.transport = transport
transport.onClose = {
this.onClose()
@ -258,13 +260,15 @@ abstract class Protocol<SendRequestT : Request, SendNotificationT : Notification
} catch (cause: Throwable) {
LOGGER.error(cause) { "Error handling request: ${request.method} (id: ${request.id})" }
transport!!.send(JSONRPCResponse(
id = request.id,
error = JSONRPCError(
ErrorCode.Defined.InternalError,
message = cause.message ?: "Internal error",
transport!!.send(
JSONRPCResponse(
id = request.id,
error = JSONRPCError(
ErrorCode.Defined.InternalError,
message = cause.message ?: "Internal error",
)
)
))
)
}
}
@ -314,7 +318,7 @@ abstract class Protocol<SendRequestT : Request, SendNotificationT : Notification
/**
* Closes the connection.
*/
suspend fun close() {
public suspend fun close() {
transport?.close()
}
@ -323,7 +327,7 @@ abstract class Protocol<SendRequestT : Request, SendNotificationT : Notification
*
* This should be implemented by subclasses.
*/
abstract fun assertCapabilityForMethod(method: Method)
protected abstract fun assertCapabilityForMethod(method: Method)
/**
* A method to check if a notification is supported by the local side, for the given method to be sent.
@ -337,14 +341,14 @@ abstract class Protocol<SendRequestT : Request, SendNotificationT : Notification
*
* This should be implemented by subclasses.
*/
abstract fun assertRequestHandlerCapability(method: Method)
public abstract fun assertRequestHandlerCapability(method: Method)
/**
* Sends a request and wait for a response.
*
* Do not use this method to emit notifications! Use notification() instead.
*/
suspend fun <T : RequestResult> request(
public suspend fun <T : RequestResult> request(
request: SendRequestT,
options: RequestOptions? = null,
): T {
@ -352,7 +356,7 @@ abstract class Protocol<SendRequestT : Request, SendNotificationT : Notification
val result = CompletableDeferred<T>()
val transport = this@Protocol.transport ?: throw Error("Not connected")
if (_options?.enforceStrictCapabilities == true) {
if (this@Protocol.options?.enforceStrictCapabilities == true) {
assertCapabilityForMethod(request.method)
}
@ -364,7 +368,7 @@ abstract class Protocol<SendRequestT : Request, SendNotificationT : Notification
progressHandlers[messageId] = options.onProgress
}
responseHandlers.set(messageId, { response, error ->
responseHandlers[messageId] = set@{ response, error ->
if (error != null) {
result.completeExceptionally(error)
return@set
@ -381,7 +385,7 @@ abstract class Protocol<SendRequestT : Request, SendNotificationT : Notification
} catch (error: Throwable) {
result.completeExceptionally(error)
}
})
}
val cancel: suspend (Throwable) -> Unit = { reason: Throwable ->
responseHandlers.remove(messageId)
@ -423,7 +427,7 @@ abstract class Protocol<SendRequestT : Request, SendNotificationT : Notification
/**
* Emits a notification, which is a one-way message that does not expect a response.
*/
suspend fun notification(notification: SendNotificationT) {
public suspend fun notification(notification: SendNotificationT) {
LOGGER.trace { "Sending notification: ${notification.method}" }
val transport = this.transport ?: error("Not connected")
assertNotificationCapability(notification.method)
@ -440,18 +444,27 @@ abstract class Protocol<SendRequestT : Request, SendNotificationT : Notification
*
* Note that this will replace any previous request handler for the same method.
*/
inline fun <reified T : Request> setRequestHandler(
public inline fun <reified T : Request> setRequestHandler(
method: Method,
noinline block: suspend (T, RequestHandlerExtra) -> SendResultT?
noinline block: suspend (T, RequestHandlerExtra) -> SendResultT?,
) {
setRequestHandler(typeOf<T>(), method, block)
}
@PublishedApi
internal fun <T : Request> setRequestHandler(
requestType: KType,
method: Method,
block: suspend (T, RequestHandlerExtra) -> SendResultT?,
) {
assertRequestHandlerCapability(method)
val type = typeOf<T>()
val serializer = McpJson.serializersModule.serializer(type)
val serializer = McpJson.serializersModule.serializer(requestType)
requestHandlers[method.value] = { request, extraHandler ->
val result = request.params?.let { McpJson.decodeFromJsonElement(serializer, it) }
val response = if (result != null) {
@Suppress("UNCHECKED_CAST")
block(result as T, extraHandler)
} else {
EmptyRequestResult()
@ -465,7 +478,7 @@ abstract class Protocol<SendRequestT : Request, SendNotificationT : Notification
/**
* Removes the request handler for the given method.
*/
fun removeRequestHandler(method: Method) {
public fun removeRequestHandler(method: Method) {
requestHandlers.remove(method.value)
}
@ -474,7 +487,7 @@ abstract class Protocol<SendRequestT : Request, SendNotificationT : Notification
*
* Note that this will replace any previous notification handler for the same method.
*/
fun <T : Notification> setNotificationHandler(method: Method, handler: (notification: T) -> Deferred<Unit>) {
public fun <T : Notification> setNotificationHandler(method: Method, handler: (notification: T) -> Deferred<Unit>) {
this.notificationHandlers[method.value] = {
@Suppress("UNCHECKED_CAST")
handler(it.fromJSON() as T)
@ -484,7 +497,7 @@ abstract class Protocol<SendRequestT : Request, SendNotificationT : Notification
/**
* Removes the notification handler for the given method.
*/
fun removeNotificationHandler(method: Method) {
public fun removeNotificationHandler(method: Method) {
this.notificationHandlers.remove(method.value)
}
}

View file

@ -10,14 +10,14 @@ import kotlinx.serialization.encodeToString
/**
* Buffers a continuous stdio stream into discrete JSON-RPC messages.
*/
class ReadBuffer {
public class ReadBuffer {
private val buffer: Buffer = Buffer()
fun append(chunk: ByteArray) {
public fun append(chunk: ByteArray) {
buffer.writeFully(chunk)
}
fun readMessage(): JSONRPCMessage? {
public fun readMessage(): JSONRPCMessage? {
if (buffer.exhausted()) return null
var lfIndex = buffer.indexOf('\n'.code.toByte())
val line = when (lfIndex) {
@ -41,7 +41,7 @@ class ReadBuffer {
return deserializeMessage(line)
}
fun clear() {
public fun clear() {
buffer.clear()
}
}

View file

@ -5,7 +5,7 @@ import org.jetbrains.kotlinx.mcp.JSONRPCMessage
/**
* Describes the minimal contract for a MCP transport that a client or server can communicate over.
*/
interface Transport {
public interface Transport {
/**
* Starts processing messages on the transport, including any connection steps that might need to be taken.
*
@ -14,24 +14,24 @@ interface Transport {
* NOTE: This method should not be called explicitly when using Client, Server, or Protocol classes,
* as they will implicitly call start().
*/
suspend fun start()
public suspend fun start()
/**
* Sends a JSON-RPC message (request or response).
*/
suspend fun send(message: JSONRPCMessage)
public suspend fun send(message: JSONRPCMessage)
/**
* Closes the connection.
*/
suspend fun close()
public suspend fun close()
/**
* Callback for when the connection is closed for any reason.
*
* This should be invoked when close() is called as well.
*/
var onClose: (() -> Unit)?
public var onClose: (() -> Unit)?
/**
* Callback for when an error occurs.
@ -39,10 +39,10 @@ interface Transport {
* Note that errors are not necessarily fatal; they are used for reporting any kind of
* exceptional condition out of band.
*/
var onError: ((Throwable) -> Unit)?
public var onError: ((Throwable) -> Unit)?
/**
* Callback for when a message (request or response) is received over the connection.
*/
var onMessage: (suspend ((JSONRPCMessage) -> Unit))?
public var onMessage: (suspend ((JSONRPCMessage) -> Unit))?
}

View file

@ -21,7 +21,7 @@ internal const val MCP_SUBPROTOCOL = "mcp"
* Abstract class representing a WebSocket transport for the Model Context Protocol (MCP).
* Handles communication over a WebSocket session.
*/
abstract class WebSocketMcpTransport : Transport {
public abstract class WebSocketMcpTransport : Transport {
private val scope by lazy {
CoroutineScope(session.coroutineContext + SupervisorJob())
}

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,3 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import org.jetbrains.kotlinx.mcp.JSONRPCMessage
import org.jetbrains.kotlinx.mcp.shared.Transport
@ -7,8 +5,6 @@ import org.jetbrains.kotlinx.mcp.shared.Transport
* In-memory transport for creating clients and servers that talk to each other within the same process.
*/
class InMemoryTransport : Transport {
private val scope = CoroutineScope(SupervisorJob())
private var otherTransport: InMemoryTransport? = null
private val messageQueue: MutableList<JSONRPCMessage> = mutableListOf()
@ -55,4 +51,4 @@ class InMemoryTransport : Transport {
other.messageQueue.add(message)
}
}
}
}

Some files were not shown because too many files have changed in this diff Show more