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.