Skip to content

feat(agents): upgrade sdk to 0.11.1, add streamable transport #1870

Open
devcrocod wants to merge 3 commits intodevelopfrom
devcrocod/update-mcp-kotlin-sdk
Open

feat(agents): upgrade sdk to 0.11.1, add streamable transport #1870
devcrocod wants to merge 3 commits intodevelopfrom
devcrocod/update-mcp-kotlin-sdk

Conversation

@devcrocod
Copy link
Copy Markdown
Contributor

@devcrocod devcrocod commented Apr 20, 2026

Upgrades MCP kotlin-sdk from 0.8.1 to 0.11.1 and makes Streamable HTTP the primary MCP transport for both client and server

closes #1674
closes KG-792
closes KG-756
closes KG-49
closes KG-755

DEPRECATED:

  • startSseMcpServer(factory, port, host, tools) -- use startMcpServer(factory, tools, port, host)
  • startSseMcpServer(factory, host, tools) -- use startMcpServer(factory, tools, host)

@devcrocod devcrocod changed the title feat(mcp): upgrade sdk to 0.11.1, add streamable transport feat(agents): upgrade sdk to 0.11.1, add streamable transport Apr 20, 2026
@devcrocod devcrocod requested a review from Copilot April 20, 2026 22:34
- Serialize non-string JSON Schema enum entries (numbers, booleans,
  null, arrays, objects) to their canonical JSON form so
  DefaultMcpToolDescriptorParser no longer throws on mixed-type enums
  like ["red", "amber", null, 42].
- Treat `additionalProperties: {}` as `true` with no type constraint
  instead of recursing into an empty schema and failing with
  "Parameter must have type property".

Closes #307
Closes #1676
@devcrocod devcrocod requested a review from tiginamaria April 20, 2026 22:36
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Upgrades the Model Context Protocol (MCP) Kotlin SDK to 0.11.1 and makes Streamable HTTP the primary transport across Koog’s MCP client/server integrations, adding parser support for newer JSON Schema constructs and expanding test coverage.

Changes:

  • Bump MCP Kotlin SDK from 0.8.1 to 0.11.1 (including examples).
  • Add Streamable HTTP client/server transport support and deprecate SSE server entrypoints in favor of startMcpServer(...).
  • Extend MCP tool schema parsing to support JSON Schema type arrays and local $ref via $defs/definitions, with new tests.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
koog-ktor/src/jvmMain/kotlin/ai/koog/ktor/KoogKtorServerPluginJvm.kt Adds Streamable HTTP registration API for MCP tools in the Ktor plugin.
gradle/libs.versions.toml Updates MCP SDK version to 0.11.1 (and related catalog cleanup).
examples/simple-examples/gradle/libs.versions.toml Updates MCP SDK version for examples to 0.11.1.
agents/agents-mcp/src/jvmTest/kotlin/ai/koog/agents/mcp/TestMcpServer.kt Adds transport-mode support + dynamic port resolution for test server.
agents/agents-mcp/src/jvmTest/kotlin/ai/koog/agents/mcp/StreamableHttpMcpToolTest.kt New tests validating Streamable HTTP tool discovery/execution.
agents/agents-mcp/src/jvmTest/kotlin/ai/koog/agents/mcp/McpToolTest.kt Switches to dynamic ports and adds error-encoding tests.
agents/agents-mcp/src/commonTest/kotlin/ai/koog/agents/mcp/DefaultMcpToolDescriptorParserTest.kt Adds extensive tests for type-arrays and $ref/$defs parsing.
agents/agents-mcp/src/commonMain/kotlin/ai/koog/agents/mcp/McpToolRegistryProvider.kt Adds Streamable HTTP registry builder and transport metadata mapping.
agents/agents-mcp/src/commonMain/kotlin/ai/koog/agents/mcp/McpToolDefinitionParser.kt Implements JSON Schema type arrays + $ref resolution in tool parsing.
agents/agents-mcp/src/commonMain/kotlin/ai/koog/agents/mcp/McpTool.kt Adjusts string encoding for error results.
agents/agents-mcp-server/src/jvmTest/kotlin/ai/koog/agents/mcp/server/KoogToolAsMcpToolTest.kt Adds Streamable HTTP server/client test path.
agents/agents-mcp-server/src/jvmMain/kotlin/ai/koog/agents/mcp/server/McpServer.jvm.kt Updates stdio server startup for new SDK session API.
agents/agents-mcp-server/src/commonMain/kotlin/ai/koog/agents/mcp/server/McpServer.kt Introduces Streamable HTTP as default server transport and deprecates SSE starters.
agents/agents-mcp-metadata/src/commonMain/kotlin/ai/koog/agents/mcp/metadata/McpToolSupport.kt Adds StreamableHttp transport metadata enum.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 160 to 167
fun stop() {
if (!isRunning) return

serverJob?.cancel()
serverJob = null
embeddedServer = null
isRunning = false
println("Test MCP server stopped")
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stop() only cancels the coroutine and nulls references, but it never stops the underlying Ktor EmbeddedServer. Since start() calls emb.start(wait = true) (blocking), cancelling serverJob may not actually stop the server, which can leak a running server and make tests hang/flaky. Consider storing emb and calling embeddedServer?.stop(gracePeriodMillis, timeoutMillis) (or using start(wait = false) and stopping explicitly) before clearing fields.

Copilot uses AI. Check for mistakes.
Comment on lines +118 to 123
level = DeprecationLevel.WARNING,
)
public suspend fun startSseMcpServer(
factory: ApplicationEngineFactory<*, *>,
host: String = "localhost",
tools: ToolRegistry,
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doStartMcpServer now always calls emb.connectors() even for callers that only need the Server (e.g. the fixed-port overload). connectors() currently busy-spins in a tight loop until resolvedConnectors() is non-empty, which can waste CPU and potentially loop forever if startup fails. Consider restoring conditional connector collection (only when the caller needs it), and/or adding a small delay/yield + timeout in connectors() to avoid a tight spin.

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +80
* Defaults to Streamable HTTP transport.
*
* @param factory The Ktor application engine factory to use (e.g., CIO, Netty).
* @param tools The tools to expose via the MCP server.
* @param host The host to bind to.
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The KDoc for startSseMcpServer(..., port, host, tools): Server says the port can be obtained from a returned list of EngineConnectorConfig, but this overload returns only Server (no connectors). Please update the comment to avoid implying data is returned here (or point to the other overload that returns connectors).

Copilot uses AI. Check for mistakes.
Comment on lines +225 to +232
val (server, connectors) = startMcpServer(
factory = CIO,
tools = ToolRegistry {
tool(tool)
},
)

val port = connectors.firstOrNull()?.port ?: 0
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Streamable HTTP test hard-codes port = 3003, which can collide with other processes/parallel test runs and cause flaky failures. Prefer allocating an available port (e.g. NetUtil.findAvailablePort() exists in this repo) and using that value when starting the server.

Copilot uses AI. Check for mistakes.
*/
override fun encodeResultToString(result: CallToolResult?, serializer: JSONSerializer): String {
if (result?.isError == true) {
return "Error: ${result.content.filterIsInstance<TextContent>().joinToString("\n") { it.text }}"
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When result.isError == true, this returns only concatenated TextContent blocks. If the server returns an error with non-text content (or an empty content list), the encoded string becomes "Error: " with no useful details. Consider falling back to encoding the whole CallToolResult (or at least result.content.toString()/JSON) when no TextContent is present, so error information isn't silently dropped.

Suggested change
return "Error: ${result.content.filterIsInstance<TextContent>().joinToString("\n") { it.text }}"
val errorText = result.content.filterIsInstance<TextContent>().joinToString("\n") { it.text }
if (errorText.isNotBlank()) {
return "Error: $errorText"
}
val fallbackErrorJson = json.encodeToJsonElement(resultSerializer, result).toKoogJSONElement()
return "Error: ${serializer.encodeJSONElementToString(fallbackErrorJson)}"

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

StreamableHttpClientTransport support

2 participants