cd /news/developer-tools/java-mcp-server-with-streamable-http… Β· home β€Ί topics β€Ί developer-tools β€Ί article
[ARTICLE Β· art-39171] src=github.com β†— pub= topic=developer-tools verified=true sentiment=↑ positive

Java MCP server with Streamable HTTP, Netty transports, and stateless deployment

Tachyon MCP, a Java 21 Model Context Protocol server built on Netty 4.2, has been released with full support for the MCP 2025-11-25 spec including Streamable HTTP transport, session lifecycle, native I/O transports, and a stateless mode for serverless deployments. The server implements JSON-RPC 2.0, pagination, CORS, DNS rebinding protection, and features like tools, resources, prompts, tasks, and logging with extensible handlers.

read4 min views1 publishedJun 25, 2026
Java MCP server with Streamable HTTP, Netty transports, and stateless deployment
Image: source

Tachyon MCP β€” A Java 21 Model Context Protocol (MCP) server built on Netty 4.2. Fully implements MCP spec 2025-11-25 Streamable HTTP transport, session lifecycle, native I/O transports, and a stateless mode for serverless deployments.

TL;DR

import dev.tachyonmcp.server.TachyonMcpServer;
import dev.tachyonmcp.server.features.tools.AbstractSyncToolHandler;
import dev.tachyonmcp.server.features.tools.ToolDescriptor;
import dev.tachyonmcp.server.features.tools.ToolResult;
import dev.tachyonmcp.server.session.McpContext;
import tools.jackson.databind.node.JsonNodeFactory;

void main() {
    var schema = JsonNodeFactory.instance.objectNode();
    schema.put("type", "object");
    schema.putObject("properties").putObject("city").put("type", "string");

    TachyonMcpServer.builder()
        .name("weather-mcp")
        .tool(new AbstractSyncToolHandler(
            ToolDescriptor.builder("get_forecast")
                .description("Get weather forecast")
                .inputSchema(schema)
                .build()) {
            @Override
            public Object handle(McpContext ctx, Object args) {
                return ToolResult.text("β˜€οΈ 22Β°C");
            }
        })
        .stateless(true)
        .port(8080)
        .bind();
}
  • JSON-RPC 2.0 β€” request/response/error/notification
  • Streamable HTTP β€” POST, GET (SSE), DELETE, OPTIONS
  • Lifecycle β€” initialize β†’ initialized β†’ ACTIVE
  • Pagination β€” cursor-based across all list methods
  • Session state machine β€” INITIALIZING β†’ ACTIVE β†’ CLOSED
  • CORS & origin validation
  • DNS rebinding protection
  • Accept header strict validation (406)
  • Pending request timeout (60s)
  • SSE resumability via Last-Event-ID

tools/list

β€” paginated withnextCursor

tools/call

β€” returnsCallToolResult

withisError

outputSchema

in listingannotations

fieldexecution.taskSupport

(forbidden/optional/required)- Synchronous & asynchronous handler interfaces

  • Name validation (1–128 chars) notifications/tools/list_changed

on add/remove- Inline notifications + logging during tool call

  • Input schema validation

resources/list

β€” paginatedresources/read

β€” text & blob contentresources/templates/list

β€” URI templatesresources/subscribe

/unsubscribe

notifications/resources/list_changed

notifications/resources/updated

to subscribers- Dynamic content via ResourceHandler

interface

prompts/list

β€” paginated withnextCursor

prompts/get

β€” invokes prompt resolvernotifications/prompts/list_changed

tasks/list

,tasks/get

,tasks/cancel

,tasks/result

  • State machine enforcement β€” SUBMITTED β†’ WORKING β†’ COMPLETED/FAILED/CANCELLED notifications/tasks/status

broadcast on every transition- Task Janitor for stale tasks execution.taskSupport

per tool (forbidden/optional/required)TasksExtension

(SEP-1686) β€” negotiable extension exposingcreate_task

tool +task://{id}

resource template- Extension-gated tool visibility (hidden from un-negotiated clients)

logging/setLevel

per sessionnotifications/message

emitted above threshold- Progress notifications

sampling/createMessage

β€” server β†’ client request- Elicitation β€” βœ… form mode; ❌ url mode notifications/cancelled

β€” bidirectionalnotifications/tasks/status

from client

  • Netty 4.2
  • io_uring / epoll / kqueue / nio auto-detection
  • Platform-thread event loops + virtual-thread handlers
  • TCP_NODELAY, SO_KEEPALIVE
  • Channel writability backpressure ( setAutoRead

) - Configurable idle timeouts (reader/writer)

Stateless modeβ€” skip sessions for serverless- IN_MEMORY session store (ConcurrentHashMap)

  • Session Janitor β€” 5s sweep, 30s TTL
  • SSE disconnect β‰  session removal (supports reconnect)
  • Event log replay on reconnection

Requirements: JDK 21+

<dependency>
    <groupId>dev.tachyonmcp</groupId>
    <artifactId>tachyon-server</artifactId>
    <version>1.0.0-alpha.2</version>
</dependency>
git clone https://github.com/kpavlov/tachyon.git
cd tachyon
mvn install -pl tachyon-mcp-server -DskipTests
python
import dev.tachyonmcp.server.TachyonMcpServer;
import dev.tachyonmcp.server.features.tools.AbstractSyncToolHandler;
import dev.tachyonmcp.server.features.tools.ToolDescriptor;
import dev.tachyonmcp.server.features.tools.ToolResult;
import dev.tachyonmcp.server.session.McpContext;
import tools.jackson.databind.node.JsonNodeFactory;

void main() {
    var schema = JsonNodeFactory.instance.objectNode();
    schema.put("type", "object");
    schema.putObject("properties").putObject("city").put("type", "string");

    TachyonMcpServer.builder()
        .name("weather-mcp")
        .tool(new AbstractSyncToolHandler(
            ToolDescriptor.builder("get_forecast")
                .description("Get weather forecast")
                .inputSchema(schema)
                .build()) {
            @Override
            public Object handle(McpContext ctx, Object args) {
                return ToolResult.text("β˜€οΈ 22Β°C");
            }
        })
        .stateless(true) // start in stateless node (no sessions)
        .port(8080) // bind to 1270.0.1:8080
        .bind();
}

With TasksExtension (negotiable) - SEP-1686

var handle = TachyonMcpServer.builder()
    .extension(new TasksExtension())   // exposes create_task tool + task://{id} resource
    .port(8080)
    .bind();

Clients that include "extensions": {"io.modelcontextprotocol/tasks": {}}

in their initialize

capabilities receive the extension's tool and resource template. Clients that don't negotiate the extension see standard MCP tasks via tasks/list

/ tasks/get

.

Handler interfaces (ToolHandler

, ResourceHandler

, PromptHandler

) and descriptor types use stable domain types. When Tachyon upgrades to a new protocol version, only the internal mapper layer changes; handler implementations are unaffected. Domain types track the 2026-07-28 spec shape where it improves on 2025-11-25 (e.g. Annotations.lastModified

, ResourceLink

in ContentBlock

).

Native transportsβ€” io_uring > epoll > kqueue > NIO auto-detect** Write buffer watermarks**β€” 32 KB low / 128 KB high, backpressure wired** Batch flushing**β€”ctx.write()

accumulates, singlectx.flush()

on boundaryMinimal allocationsβ€”McpEndpointHandler

is@Sharable

, no per-request handler creationVirtual threadsβ€” handlers offloaded from event loop, no manual thread pools** JSON-RPC**β€” Jackson streaming codec, no ObjectMapper.

Rate limitingβ€” Not yet implemented - URL elicitation mode / -32042 errorβ€” Form mode works, URL mode missing - 2026-07-28 draft protocol versionβ€” Not negotiable; version-gated features ready - Stale session on re-initializeβ€” 30s TTL lingering, affects reconnect only

Yes. Use stateless(true)

to skip session persistence. Each invocation processes one request independently.

Not yet. The current pipeline targets HTTP/1.1; HTTP/2 upgrade is a pipeline configuration change comming soon.

Extend AbstractSyncToolHandler

or AbstractAsyncToolHandler

, passing a ToolDescriptor

:

class MyTool extends AbstractSyncToolHandler {
    MyTool() {
        super(ToolDescriptor.builder("my_tool")
                .description("Does something useful")
                .inputSchema(buildSchema())
                .build());
    }

    @Override
    public Object handle(McpContext ctx, Object args) throws Exception {
        return CallToolResult.ofText("done");
    }

    private static JsonNode buildSchema() {
        var s = JsonNodeFactory.instance.objectNode();
        s.put("type", "object");
        return s;
    }
}
server.resources().add(
    ResourceDescriptor.of("custom://data"),
    (ctx, req) -> new TextResourceContents("content", req.uri(), "text/plain", null));

Tachyon MCP is available under the terms of the Apache 2.0.

── more in #developer-tools 4 stories Β· sorted by recency
── more on @tachyon mcp 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/java-mcp-server-with…] indexed:0 read:4min 2026-06-25 Β· β€”