-
Notifications
You must be signed in to change notification settings - Fork 175
Description
MCP Kotlin SDK 0.7.4 Bug Report: Empty POST Response causing "Cannot read Json element" Error
Summary
MCP Kotlin SDK 0.7.4 successfully establishes SSE connections and starts transport, but returns empty responses to POST requests from MCP clients (specifically Cursor), causing JSON parsing errors and preventing successful MCP communication.
Environment
- SDK Version:
io.modelcontextprotocol:kotlin-sdk:0.7.4 - Ktor Version: 3.0.2
- Ktor Engine: CIO
- JVM: Amazon Corretto 21
- OS: macOS 24.6.0
- Client: Cursor (Anthropic MCP Client)
- Date: October 31, 2025
Description
When connecting an MCP client (Cursor) to a server built with MCP Kotlin SDK 0.7.4, the following sequence occurs:
- ✅ SSE connection establishes successfully - Server logs show:
New SSE connection established and stored with sessionId - ✅ Tools and resources register - Server logs show:
Registering tool: xxx - ✅ Transport starts - Server logs show:
Starting transport - ❌ POST message handling fails - Client receives empty HTTP 400 response body
Client Error:
Error POSTing to endpoint (HTTP 400):
Error handling message : Cannot read Json element because of unexpected end of the input at path: $
JSON input:
(empty)
Reproduction Steps
Minimal Test Server
Created a minimal Hello World MCP server based on official documentation:
package kr.co.metadata.mcp.test
import io.ktor.server.application.*
import io.ktor.server.cio.*
import io.ktor.server.engine.*
import io.modelcontextprotocol.kotlin.sdk.*
import io.modelcontextprotocol.kotlin.sdk.server.*
fun main() {
embeddedServer(CIO, port = 3057, host = "127.0.0.1") {
// MCP at Application level (routing doesn't work!)
mcp {
Server(
serverInfo = Implementation(
name = "hello-world-test-server",
version = "1.0.0"
),
options = ServerOptions(
capabilities = ServerCapabilities(
tools = ServerCapabilities.Tools(listChanged = true),
resources = ServerCapabilities.Resources(subscribe = true, listChanged = true),
prompts = ServerCapabilities.Prompts(listChanged = true)
)
)
) {
"This is a simple Hello World MCP test server"
}.apply {
// Add a simple hello_world tool
addTool(
name = "hello_world",
description = "Returns a hello world message",
inputSchema = Tool.Input()
) { _ ->
CallToolResult(
content = listOf(
TextContent(
text = "Hello, World from MCP Test Server!"
)
),
isError = false
)
}
}
}
}.start(wait = true)
}MCP Client Configuration (mcp.json)
{
"mcpServers": {
"HelloWorldTest": {
"url": "http://127.0.0.1:3057/"
}
}
}Expected Behavior
- Client connects to SSE endpoint at
http://127.0.0.1:3057/ - Client receives
sessionIdvia SSE (✅ Works) - Client sends POST request with
initializemessage - Server responds with proper JSON containing server info and capabilities
- Client displays available tools (e.g.,
hello_world)
Actual Behavior
- Client connects to SSE endpoint ✅
- Client receives
sessionId✅ - Client sends POST request with
initializemessage ✅ - Server responds with HTTP 400 and EMPTY body ❌
- Client fails to parse empty JSON and shows error ❌
- No tools are displayed in client ❌
Server Logs (Success until POST handling)
01:11:15.975 INFO [main] io.ktor.server.Application | Autoreload is disabled because the development mode is off.
01:11:16.001 INFO [main] io.ktor.server.Application | Application started in 0.108 seconds.
01:11:16.012 INFO [DefaultDispatcher-worker-2] io.ktor.server.Application | Responding at http://127.0.0.1:3057
01:11:22.995 INFO [DefaultDispatcher-worker-1] i.m.kotlin.sdk.server.KtorServer | New SSE connection established and stored with sessionId: b421d2a3-ad4d-4f83-b190-2542e52c099b
01:11:22.999 INFO [DefaultDispatcher-worker-1] i.m.kotlin.sdk.server.Server | Registering tool: hello_world
01:11:23.000 INFO [DefaultDispatcher-worker-1] i.m.kotlin.sdk.server.Server | Registering resource: Hello Resource (test://hello)
✅ Hello World MCP server configured with 1 tool and 1 resource
01:11:23.013 INFO [DefaultDispatcher-worker-1] i.m.kotlin.sdk.shared.Protocol | Starting transport
Client Logs (Cursor)
2025-10-31 01:12:07.900 [info] Handling CreateClient action
2025-10-31 01:12:07.900 [info] Creating streamableHttp transport
2025-10-31 01:12:07.900 [info] Connecting to streamableHttp server
2025-10-31 01:12:07.908 [info] No stored tokens found
2025-10-31 01:12:07.917 [error] Client error for command Error POSTing to endpoint (HTTP 400): sessionId query parameter is not provided
2025-10-31 01:12:07.917 [info] Client closed for command
2025-10-31 01:12:07.917 [error] Error connecting to streamableHttp server, falling back to SSE: Error POSTing to endpoint (HTTP 400): sessionId query parameter is not provided
2025-10-31 01:12:07.917 [error] Error connecting to streamableHttp server, falling back to SSE: Error POSTing to endpoint (HTTP 400): sessionId query parameter is not provided
2025-10-31 01:12:07.917 [info] Connecting to SSE server
2025-10-31 01:12:07.924 [info] No stored tokens found
2025-10-31 01:12:07.932 [info] No stored tokens found
2025-10-31 01:12:07.935 [error] Client error for command Error POSTing to endpoint (HTTP 400): Error handling message : Cannot read Json element because of unexpected end of the input at path: $
JSON input:
2025-10-31 01:12:07.935 [info] Client closed for command
2025-10-31 01:12:07.935 [error] Error connecting to SSE server after fallback: Error POSTing to endpoint (HTTP 400): Error handling message : Cannot read Json element because of unexpected end of the input at path: $
JSON input:
2025-10-31 01:12:07.943 [info] Handling ListOfferings action, server stored: false
2025-10-31 01:12:07.943 [error] No server info found
Additional Issue: Routing Documentation Incorrect
The official README shows this example for custom routing:
fun Application.module() {
install(SSE)
routing {
route("myRoute") {
mcp {
Server(...)
}
}
}
}This does NOT work. Testing shows:
- ✅
mcp {}atApplicationlevel (root/): 200 OK - ❌
mcp {}insideroute("/sse"): 404 Not Found
The mcp {} function appears to be an Application extension, not a Route extension, contradicting the documentation.
Manual Testing Results
# SSE endpoint works - returns sessionId
$ curl http://127.0.0.1:3057/ -N -H "Accept: text/event-stream"
event: endpoint
data: ?sessionId=038c4917-a740-4bec-9bc9-c4946e77294c
# POST with sessionId returns "Session not found" (expected for invalid session, but proves endpoint responds)
$ curl -X POST "http://127.0.0.1:3057/?sessionId=test-session" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
Session not found%Impact
This bug makes MCP Kotlin SDK 0.7.4 completely unusable with MCP clients:
- No tools can be registered or called
- No resources can be accessed
- No prompts can be used
- Clients show "Error" status and "No server info found"
Workaround
We suspect SDK 0.6.0 may work correctly based on the release notes showing that 0.7.0 introduced significant changes to SSE handling. We have not tested this yet.
Request
- Fix the empty POST response issue - The SDK should return proper JSON responses to client messages
- Clarify routing documentation - Either fix
route()support or update docs to show it only works at Application level - Add integration tests - Test with actual MCP clients (Cursor, Claude Desktop, etc.) to catch these issues
Full Source Code
Available at: https://github.com/[your-repo]/MCP-Magic (can provide if needed)
Thank you for maintaining this SDK! We're building production applications on top of MCP and would love to help resolve this critical issue.