New MCP Schema types (#386)

Implement the last version of the mcp schema and complete restructuring
of the mcp schema implementation from a monolithic `types.kt` file into
separate files.

- #158 
- #185 
- #353 
- #298 
- #248 

## Motivation and Context
The previous implementation of the mcp schema was outdated and contained
several issues. In addition, it stored all mcp types in a single large
file, which made maintenance and further development more difficult

## How Has This Been Tested?
All tests passed

## Breaking Changes
Yes. The new types are now located in a separate package. Additionally,
many types have new parameters and modified constructors

## Types of changes
- [x] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed

## Additional context
to adopt the new structure:
**Before:**
```kotlin
  import io.modelcontextprotocol.kotlin.sdk.InitializeRequest
  import io.modelcontextprotocol.kotlin.sdk.Resource
```

**After:**
```kotlin
  import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest
  import io.modelcontextprotocol.kotlin.sdk.types.Resource
```
This commit is contained in:
Pavel Gorgulov 2025-11-11 12:25:23 +01:00 • committed by GitHub
commit 21393e7ce3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 13048 additions and 4230 deletions

View file

@ -1,14 +1,14 @@
package io.modelcontextprotocol.kotlin.sdk.server
import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
import io.modelcontextprotocol.kotlin.sdk.CallToolResult
import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest
import io.modelcontextprotocol.kotlin.sdk.GetPromptResult
import io.modelcontextprotocol.kotlin.sdk.Prompt
import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest
import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult
import io.modelcontextprotocol.kotlin.sdk.Resource
import io.modelcontextprotocol.kotlin.sdk.Tool
import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult
import io.modelcontextprotocol.kotlin.sdk.types.Prompt
import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest
import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult
import io.modelcontextprotocol.kotlin.sdk.types.Resource
import io.modelcontextprotocol.kotlin.sdk.types.Tool
internal typealias FeatureKey = String

View file

@ -8,9 +8,9 @@ import io.ktor.server.request.contentType
import io.ktor.server.request.receiveText
import io.ktor.server.response.respondText
import io.ktor.server.sse.ServerSSESession
import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport
import io.modelcontextprotocol.kotlin.sdk.shared.McpJson
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage
import io.modelcontextprotocol.kotlin.sdk.types.McpJson
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.job
import kotlin.concurrent.atomics.AtomicBoolean

View file

@ -1,32 +1,32 @@
package io.modelcontextprotocol.kotlin.sdk.server
import io.github.oshai.kotlinlogging.KotlinLogging
import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
import io.modelcontextprotocol.kotlin.sdk.CallToolResult
import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject
import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest
import io.modelcontextprotocol.kotlin.sdk.GetPromptResult
import io.modelcontextprotocol.kotlin.sdk.Implementation
import io.modelcontextprotocol.kotlin.sdk.ListPromptsRequest
import io.modelcontextprotocol.kotlin.sdk.ListPromptsResult
import io.modelcontextprotocol.kotlin.sdk.ListResourceTemplatesRequest
import io.modelcontextprotocol.kotlin.sdk.ListResourceTemplatesResult
import io.modelcontextprotocol.kotlin.sdk.ListResourcesRequest
import io.modelcontextprotocol.kotlin.sdk.ListResourcesResult
import io.modelcontextprotocol.kotlin.sdk.ListToolsRequest
import io.modelcontextprotocol.kotlin.sdk.ListToolsResult
import io.modelcontextprotocol.kotlin.sdk.Method
import io.modelcontextprotocol.kotlin.sdk.Prompt
import io.modelcontextprotocol.kotlin.sdk.PromptArgument
import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest
import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult
import io.modelcontextprotocol.kotlin.sdk.Resource
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.TextContent
import io.modelcontextprotocol.kotlin.sdk.Tool
import io.modelcontextprotocol.kotlin.sdk.ToolAnnotations
import io.modelcontextprotocol.kotlin.sdk.shared.ProtocolOptions
import io.modelcontextprotocol.kotlin.sdk.shared.Transport
import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest
import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsResult
import io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest
import io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesResult
import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest
import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult
import io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest
import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult
import io.modelcontextprotocol.kotlin.sdk.types.Method
import io.modelcontextprotocol.kotlin.sdk.types.Prompt
import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument
import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest
import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult
import io.modelcontextprotocol.kotlin.sdk.types.Resource
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.types.TextContent
import io.modelcontextprotocol.kotlin.sdk.types.Tool
import io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations
import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
import kotlinx.atomicfu.atomic
import kotlinx.atomicfu.update
import kotlinx.collections.immutable.persistentListOf
@ -245,7 +245,7 @@ public open class Server(
* @param inputSchema The expected input schema for the tool.
* @param outputSchema The optional expected output schema for the tool.
* @param toolAnnotations Optional additional tool information.
* @param _meta Optional metadata as a [JsonObject].
* @param meta Optional metadata as a [JsonObject].
* @param handler A suspend function that handles executing the tool when called by the client.
* @throws IllegalStateException If the server does not support tools.
*/
@ -253,21 +253,21 @@ public open class Server(
public fun addTool(
name: String,
description: String,
inputSchema: Tool.Input = Tool.Input(),
inputSchema: ToolSchema = ToolSchema(),
title: String? = null,
outputSchema: Tool.Output? = null,
outputSchema: ToolSchema? = null,
toolAnnotations: ToolAnnotations? = null,
@Suppress("LocalVariableName") _meta: JsonObject? = null,
meta: JsonObject? = null,
handler: suspend (CallToolRequest) -> CallToolResult,
) {
val tool = Tool(
name = name,
title = title,
description = description,
inputSchema = inputSchema,
outputSchema = outputSchema,
description = description,
title = title,
annotations = toolAnnotations,
_meta = _meta ?: EmptyJsonObject,
meta = meta,
)
addTool(tool, handler)
}
@ -474,13 +474,14 @@ public open class Server(
}
private suspend fun handleCallTool(request: CallToolRequest): CallToolResult {
logger.debug { "Handling tool call request for tool: ${request.name}" }
val requestParams = request.params
logger.debug { "Handling tool call request for tool: ${requestParams.name}" }
// Check if the tool exists
val tool = toolRegistry.get(request.name) ?: run {
logger.error { "Tool not found: ${request.name}" }
val tool = toolRegistry.get(requestParams.name) ?: run {
logger.error { "Tool not found: ${requestParams.name}" }
return CallToolResult(
content = listOf(TextContent(text = "Tool ${request.name} not found")),
content = listOf(TextContent(text = "Tool ${requestParams.name} not found")),
isError = true,
)
}
@ -488,14 +489,14 @@ public open class Server(
@Suppress("TooGenericExceptionCaught")
// Execute the tool handler and catch any errors
return try {
logger.trace { "Executing tool ${request.name} with input: ${request.arguments}" }
logger.trace { "Executing tool ${requestParams.name} with input: ${requestParams.arguments}" }
tool.handler(request)
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
logger.error(e) { "Error executing tool ${request.name}" }
logger.error(e) { "Error executing tool ${requestParams.name}" }
CallToolResult(
content = listOf(TextContent(text = "Error executing tool ${request.name}: ${e.message}")),
content = listOf(TextContent(text = "Error executing tool ${requestParams.name}: ${e.message}")),
isError = true,
)
}
@ -507,11 +508,12 @@ public open class Server(
}
private suspend fun handleGetPrompt(request: GetPromptRequest): GetPromptResult {
logger.debug { "Handling get prompt request for: ${request.name}" }
val prompt = promptRegistry.get(request.name)
val requestParams = request.params
logger.debug { "Handling get prompt request for: ${requestParams.name}" }
val prompt = promptRegistry.get(requestParams.name)
?: run {
logger.error { "Prompt not found: ${request.name}" }
throw IllegalArgumentException("Prompt not found: ${request.name}")
logger.error { "Prompt not found: ${requestParams.name}" }
throw IllegalArgumentException("Prompt not found: ${requestParams.name}")
}
return prompt.messageProvider(request)
}
@ -522,11 +524,12 @@ public open class Server(
}
private suspend fun handleReadResource(request: ReadResourceRequest): ReadResourceResult {
logger.debug { "Handling read resource request for: ${request.uri}" }
val resource = resourceRegistry.get(request.uri)
val requestParams = request.params
logger.debug { "Handling read resource request for: ${requestParams.uri}" }
val resource = resourceRegistry.get(requestParams.uri)
?: run {
logger.error { "Resource not found: ${request.uri}" }
throw IllegalArgumentException("Resource not found: ${request.uri}")
logger.error { "Resource not found: ${requestParams.uri}" }
throw IllegalArgumentException("Resource not found: ${requestParams.uri}")
}
return resource.readHandler(request)
}

View file

@ -1,33 +1,36 @@
package io.modelcontextprotocol.kotlin.sdk.server
import io.github.oshai.kotlinlogging.KotlinLogging
import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities
import io.modelcontextprotocol.kotlin.sdk.CreateElicitationRequest
import io.modelcontextprotocol.kotlin.sdk.CreateElicitationRequest.RequestedSchema
import io.modelcontextprotocol.kotlin.sdk.CreateElicitationResult
import io.modelcontextprotocol.kotlin.sdk.CreateMessageRequest
import io.modelcontextprotocol.kotlin.sdk.CreateMessageResult
import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject
import io.modelcontextprotocol.kotlin.sdk.EmptyRequestResult
import io.modelcontextprotocol.kotlin.sdk.Implementation
import io.modelcontextprotocol.kotlin.sdk.InitializeRequest
import io.modelcontextprotocol.kotlin.sdk.InitializeResult
import io.modelcontextprotocol.kotlin.sdk.InitializedNotification
import io.modelcontextprotocol.kotlin.sdk.LATEST_PROTOCOL_VERSION
import io.modelcontextprotocol.kotlin.sdk.ListRootsRequest
import io.modelcontextprotocol.kotlin.sdk.ListRootsResult
import io.modelcontextprotocol.kotlin.sdk.LoggingLevel
import io.modelcontextprotocol.kotlin.sdk.LoggingMessageNotification
import io.modelcontextprotocol.kotlin.sdk.Method
import io.modelcontextprotocol.kotlin.sdk.Method.Defined
import io.modelcontextprotocol.kotlin.sdk.PingRequest
import io.modelcontextprotocol.kotlin.sdk.PromptListChangedNotification
import io.modelcontextprotocol.kotlin.sdk.ResourceListChangedNotification
import io.modelcontextprotocol.kotlin.sdk.ResourceUpdatedNotification
import io.modelcontextprotocol.kotlin.sdk.SUPPORTED_PROTOCOL_VERSIONS
import io.modelcontextprotocol.kotlin.sdk.ToolListChangedNotification
import io.modelcontextprotocol.kotlin.sdk.shared.Protocol
import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions
import io.modelcontextprotocol.kotlin.sdk.types.BaseRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest
import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult
import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest
import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.ElicitResult
import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest
import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult
import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification
import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION
import io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest
import io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult
import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel
import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification
import io.modelcontextprotocol.kotlin.sdk.types.Method
import io.modelcontextprotocol.kotlin.sdk.types.Method.Defined
import io.modelcontextprotocol.kotlin.sdk.types.PingRequest
import io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification
import io.modelcontextprotocol.kotlin.sdk.types.RequestMeta
import io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification
import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification
import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS
import io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest
import io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification
import kotlinx.atomicfu.AtomicRef
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CompletableDeferred
@ -81,10 +84,10 @@ public open class ServerSession(
// Logging level handler
if (options.capabilities.logging != null) {
setRequestHandler<LoggingMessageNotification.SetLevelRequest>(Defined.LoggingSetLevel) { request, _ ->
currentLoggingLevel.value = request.level
logger.debug { "Logging level set to: ${request.level}" }
EmptyRequestResult()
setRequestHandler<SetLevelRequest>(Defined.LoggingSetLevel) { request, _ ->
currentLoggingLevel.value = request.params.level
logger.debug { "Logging level set to: ${request.params.level}" }
EmptyResult()
}
}
}
@ -125,7 +128,7 @@ public open class ServerSession(
* @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 = request(PingRequest())
public suspend fun ping(): EmptyResult = request(PingRequest())
/**
* Creates a message using the server's sampling capability.
@ -140,8 +143,7 @@ public open class ServerSession(
options: RequestOptions? = null,
): CreateMessageResult {
logger.debug {
"Creating message with ${params.messages.size} messages, maxTokens=${params.maxTokens}, " +
"temperature=${params.temperature}, systemPrompt=${if (params.systemPrompt != null) "present" else "absent"}"
"Creating message with ${params.params.messages.size} messages, maxTokens=${params.params.maxTokens}, temperature=${params.params.temperature}, systemPrompt=${if (params.params.systemPrompt != null) "present" else "absent"}"
}
logger.trace { "Full createMessage params: $params" }
return request(params, options)
@ -160,20 +162,20 @@ public open class ServerSession(
options: RequestOptions? = null,
): ListRootsResult {
logger.debug { "Listing roots with params: $params" }
return request(ListRootsRequest(params), options)
return request(ListRootsRequest(BaseRequestParams(RequestMeta(params))), options)
}
public suspend fun createElicitation(
message: String,
requestedSchema: RequestedSchema,
requestedSchema: ElicitRequestParams.RequestedSchema,
options: RequestOptions? = null,
): CreateElicitationResult {
): ElicitResult {
logger.debug {
"Creating elicitation with message length=${message.length}, " +
"schema properties count=${requestedSchema.properties.size}"
}
logger.trace { "Full elicitation message: $message, requestedSchema: $requestedSchema" }
return request(CreateElicitationRequest(message, requestedSchema), options)
return request(ElicitRequest(ElicitRequestParams(message, requestedSchema)), options)
}
/**
@ -336,7 +338,7 @@ public open class ServerSession(
logger.trace { "Asserting request handler capability for method: ${method.value}" }
when (method) {
Defined.SamplingCreateMessage -> {
if (serverCapabilities.sampling == null) {
if (serverCapabilities.experimental?.get("sampling") == null) {
logger.error { "Server capability assertion failed: sampling not supported" }
throw IllegalStateException("Server does not support sampling (required for $method)")
}
@ -388,10 +390,10 @@ public open class ServerSession(
private suspend fun handleInitialize(request: InitializeRequest): InitializeResult {
logger.debug { "Handling initialization request from client" }
clientCapabilities = request.capabilities
clientVersion = request.clientInfo
clientCapabilities = request.params.capabilities
clientVersion = request.params.clientInfo
val requestedVersion = request.protocolVersion
val requestedVersion = request.params.protocolVersion
val protocolVersion = if (SUPPORTED_PROTOCOL_VERSIONS.contains(requestedVersion)) {
requestedVersion
} else {
@ -418,7 +420,7 @@ public open class ServerSession(
private fun isMessageIgnored(level: LoggingLevel): Boolean {
val current = currentLoggingLevel.value ?: return false // If no level is set, don't filter
return level.ordinal < current.ordinal
return level < current
}
/**

View file

@ -1,11 +1,11 @@
package io.modelcontextprotocol.kotlin.sdk.server
import io.github.oshai.kotlinlogging.KotlinLogging
import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
import io.modelcontextprotocol.kotlin.sdk.internal.IODispatcher
import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport
import io.modelcontextprotocol.kotlin.sdk.shared.ReadBuffer
import io.modelcontextprotocol.kotlin.sdk.shared.serializeMessage
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable

View file

@ -11,10 +11,10 @@ import io.ktor.server.websocket.WebSockets
import io.ktor.server.websocket.webSocket
import io.ktor.utils.io.CancellationException
import io.ktor.utils.io.KtorDsl
import io.modelcontextprotocol.kotlin.sdk.Implementation
import io.modelcontextprotocol.kotlin.sdk.LIB_VERSION
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
import kotlinx.coroutines.awaitCancellation
private val logger = KotlinLogging.logger {}