Tachyon MCP is a Java 21 Model Context Protocol (MCP) server built on Netty. Implements the 2025-11-25 Streamable HTTP transport, protocol extensions, and stateless mode.
- MCP Spec Compliant β All official conformance tests green on the current 2025-11-25 spec; tools, resources, prompts, tasks, elicitation, sampling, and extensions (SEP-1686, SEP-2133) in one artifact.
- Your handlers outlive the spec β MCP moves fast; Tachyon's stable domain types (
ToolHandler,ResourceHandler,PromptHandler, Task support) absorb protocol changes in an internal mapper layer, so version bumps don't touch your code. - Write blocking code, get async runtime β handlers run on Java 21 virtual threads off the Netty event loop; plain synchronous logic scales without thread pools, reactive chains, or
CompletableFuturegymnastics. Coroutine-first Kotlin DSL included. - Serverless-ready β stateless by default, so each request is independent for AWS Lambda and friends; opt into full session mode (
.session(s -> s.enabled(true))) for SSE resumability, Last-Event-ID replay, TTL + janitor, and a customizable session-id generator. - Production-grade transport β Netty core with backpressure watermarks, graceful shutdown, DNS-rebinding protection, and auto-detected native transports (io_uring / epoll / kqueue) when you want the extra speed.
TL;DR
-
Add dependency:
<dependency> <groupId>dev.tachyonmcp</groupId> <artifactId>tachyon-server</artifactId> <version>1.0.0-beta.3</version> </dependency>
-
Create MCP server:
import dev.tachyonmcp.server.TachyonServer; import dev.tachyonmcp.runtime.InteractionContext; import dev.tachyonmcp.server.features.tools.AbstractSyncToolHandler; import dev.tachyonmcp.server.features.tools.ToolArgs; import dev.tachyonmcp.server.features.tools.ToolDescriptor; import dev.tachyonmcp.server.features.tools.ToolResult; 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"); TachyonServer.builder() .name("weather-mcp") .tool(new AbstractSyncToolHandler( ToolDescriptor.builder("get_forecast") .description("Get weather forecast") .inputSchema(schema) .build()) { @Override public ToolResult handle(InteractionContext ctx, ToolArgs args) { return ToolResult.text("βοΈ 22Β°C"); } }) .port(8080) .start(); }
| Guide | Description |
|---|---|
| Quickstart | Build a working server in 5 minutes |
| Configuration | Network, I/O engine (native transports), sessions, CORS |
| Tools | Sync/async handlers, input schema, ToolResult |
| Resources | Static URIs, dynamic handlers, URI templates |
| Tasks | Long-running operations, state machine, TasksExtension |
| Extensions | Custom protocol extensions, negotiation |
| Kotlin DSL | Coroutine-first DSL, TachyonServer { }, scope reference |
| Kotlin module | tachyon-server-kotlin module overview |
Add agent skill to write better code using this SDK:
npx skills add kpavlov/tachyon --skill tachyon-mcpThe skill includes compilable Java and Kotlin example sources under .agents/skills/tachyon-mcp/resources/.
They are compiled as extra source roots of the e2e module during mvn test to keep them valid.
Check out Skills CLI for more options.
- 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)
- Extensions (SEP-2133)
tools/listβ paginated withnextCursortools/callβ returnsCallToolResultwithisErroroutputSchemain listingannotationsfieldexecution.taskSupport(forbidden/optional/required)- Synchronous & asynchronous handler interfaces
- Tool name validation (SEP-986)
notifications/tools/list_changedon add/remove- Inline notifications + logging during tool call
- Input JSON Schema 2020-12 validation (SEP-1613)
resources/listβ paginatedresources/readβ text & blob contentresources/templates/listβ URI templatesresources/subscribe/unsubscribenotifications/resources/list_changednotifications/resources/updatedto subscribers- Dynamic content via
ResourceHandlerinterface
prompts/listβ paginated withnextCursorprompts/getβ invokes prompt resolvernotifications/prompts/list_changed
tasks/list,tasks/get,tasks/cancel,tasks/result- State machine enforcement β SUBMITTED β WORKING β INPUT_REQUIRED β COMPLETED/FAILED/CANCELLED, plus REJECTED/AUTH_REQUIRED
notifications/tasks/statusbroadcast on every transition- Task Janitor for stale tasks
execution.taskSupportper tool (forbidden/optional/required)TasksExtension(SEP-1686) β negotiable extension exposingcreate_tasktool +task://{id}resource template- Extension-gated tool visibility (hidden from un-negotiated clients)
logging/setLevelper sessionnotifications/messageemitted above threshold- Progress notifications
sampling/createMessageβ server β client request- Elicitation β β form mode; β url mode
notifications/cancelledβ bidirectionalnotifications/tasks/statusfrom 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)
- SSE resumability via Last-Event-ID
- 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
- Graceful shutdown β drains in-flight handlers for
shutdownGracePeriod(default 5s) before force-interrupt
Requirements: JDK 21+
git clone https://github.com/kpavlov/tachyon.git
cd tachyon
mvn install -pl tachyon-server -DskipTestsSee docs/quickstart.md for a full walkthrough with Java and Kotlin examples, curl test, and next-step links.
var handle = TachyonServer.builder()
.extension(TasksExtension.instance()) // exposes create_task tool + task://{id} resource
.port(8080)
.start();Clients that include "extensions": {"io.modelcontextprotocol/tasks": {}} in their initialize capabilities receive the extension's tool and resource template. Clients that don't negotiate it see standard tasks/* methods. See docs/tasks.md.
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 boundary - Minimal allocations β
McpEndpointHandleris@Sharable, no per-request handler creation - Virtual threads β handlers offloaded from event loop, no manual thread pools
- JSON-RPC β Jackson streaming codec, no ObjectMapper.
- Rate limiting β Not yet implemented
- 2026-07-28 draft protocol version β High priority
- Stale session on re-initialize β 30s TTL lingering, affects reconnect only
Yes. Servers are stateless by default, so each invocation processes one request independently. Enable sessions with .session(cfg -> cfg.enabled(true)) when you need SSE resumability or replay.
Not yet. The current transport targets HTTP/1.1.
See docs/tools.md β covers lambda and class-based handlers, input schema, and ToolResult factories.
See docs/resources.md β covers static URIs, dynamic handlers, URI templates, and subscriptions.
Tachyon MCP is available under the terms of the Apache 2.0.