Tachyon MCP: A Spec-Forward Java Runtime for the MCP Ecosystem

Tachyon MCP: A Spec-Forward Java Runtime for the MCP Ecosystem

Model Context Protocol (MCP) is the REST API of the AI era — the universal interface between agents and the tools they use. The protocol is also evolving fast: new drafts, experimental features, and Specification Enhancement Proposals (SEPs) are landing regularly. For teams building on the JVM, keeping up means staying close to the spec.

The Java ecosystem already has good options, and they stack in layers. At the base, the official MCP Java SDK is a clean protocol library you can embed anywhere. On top of it, Spring AI MCP gives you a full server integrated with the Spring ecosystem, and the Quarkus team has built a polished MCP extension for the Quarkus stack. Each is the right tool in its context.

I built Tachyon MCP Runtime to sit in a different spot in that stack: a standalone, pure-Java MCP server runtime — more than a protocol library, but without pulling in a framework. It focuses on tracking the emerging spec closely and giving you an extensible engine for AI workloads.

The goal: stay current with the spec

MCP is not a stable protocol yet. The 2025-11-25 spec introduced tasks as an experimental feature. The upcoming 2026-07-28 draft promotes Tasks to a negotiable extension. SEPs like elicitation (SEP-1034, SEP-1330) add new interaction patterns. A server that’s correct today may lag behind by next quarter.

Tachyon is designed to track these changes quickly. The extension mechanism is first-class — adding support for a new SEP or draft feature means registering an McpExtension, not forking the core. My goal is for Tachyon to be a place where emerging MCP capabilities land early.

Five lines to a running server

Add Maven dependency:

xml
1<dependency>
2    <groupId>dev.tachyonmcp</groupId>
3    <artifactId>tachyon-server</artifactId>
4    <version>1.0.0-beta.4</version>
5</dependency>

(check Maven Central for latest version)

Start your MCP server with your tool:

java
1var handle = TachyonServer.builder()
2    .name("weather-mcp")
3    .tool(myWeatherTool)
4    .port(8080)
5    .start();

That’s 2025-11-25 spec-compliant MCP server: JSON-RPC 2.0, Streamable HTTP (POST + SSE + DELETE + OPTIONS), session lifecycle, DNS rebinding protection, and CORS — all configured with sensible defaults. No framework, no annotation processing, no dependency injection container required.

Writing your first tool

Tools are the primary unit of work. Extend AbstractSyncToolHandler for simple request/response logic:

java
 1class GetWeatherTool extends AbstractSyncToolHandler {
 2
 3    GetWeatherTool() {
 4        super(ToolDescriptor.builder("get_weather", """
 5                {
 6                  "type": "object",
 7                  "properties": {
 8                    "city": { "type": "string", "description": "City name" }
 9                  },
10                  "required": ["city"]
11                }
12                """, null)
13            .description("Get current weather for a city")
14            .build());
15    }
16
17    @Override
18    public ToolResult handle(InteractionContext ctx, ToolArgs args) {
19        var city = args.string("city");
20        return ToolResult.text("🌤️ 22°C in " + city);
21    }
22}

For tools that call downstream APIs or run database queries, the sync handler is still the right choice — blocking is fine because Tachyon runs every handler on a JDK 21 virtual thread, not on a Netty I/O thread. A Thread.sleep, a JDBC query, or a synchronous HTTP call parks the virtual thread without touching the event loop:

java
 1class ForecastApiTool extends AbstractSyncToolHandler {
 2
 3    ForecastApiTool() {
 4        super(ToolDescriptor.builder("get_forecast", """
 5                {
 6                  "type": "object",
 7                  "properties": {
 8                    "city": { "type": "string", "description": "City name" }
 9                  },
10                  "required": ["city"]
11                }
12                """, null)
13            .description("Get current weather forecast")
14            .build());
15    }
16
17    @Override
18    public ToolResult handle(InteractionContext ctx, ToolArgs args) {
19        var city = args.string("city");
20        var result = weatherApi.fetch(city);  // blocking call — safe on a virtual thread
21        return ToolResult.text(result.summary());
22    }
23}

AsyncToolHandler exists for integrating clients that already return a CompletionStage (Reactor, Vert.x, async HTTP clients). If you’re writing new code, sync is simpler and costs nothing extra.

What’s already spec-compliant

Tachyon passes 46 out of 46 tests on the official @modelcontextprotocol/[email protected] runner against MCP spec 2025-11-25. The conformance suite covers the core protocol:

  • Protocol basics — JSON-RPC 2.0, protocol version negotiation, pending request timeout, max request body (1 MB), and strict Accept header validation returning 406 on mismatch.
  • Tools, Resources, Prompts — paginated list endpoints with nextCursor, tools/call with isError and structured output, resources/subscribe/unsubscribe with live update notifications, prompts/get with argument resolution.
  • Session management — SSE disconnect doesn’t remove the session. Clients reconnect with Last-Event-ID and the server replays the event log from that point. Session TTL is configurable (default 30s).
  • Security — DNS rebinding protection and origin validation out of the box.
  • Input validation — JSON Schema 2020-12 validation on tool and prompt arguments before your handler is called.

Beyond the conformance suite, Tachyon also implements features the runner does not yet test:

  • Tasks — the 2025-11-25 spec defines tasks as experimental. Tachyon implements the full state machine (SUBMITTED → WORKING → COMPLETED/FAILED/CANCELLED) with tasks/cancel, tasks/result, and notifications/tasks/status broadcast on every transition.
  • Elicitation — lets the server request structured input from the user mid-conversation.

Tasks: bridging two specs

The MCP spec is moving fast. In 2025-11-25, tasks are an experimental first-class concept. In the upcoming 2026-07-28 draft, they become a negotiable extension (SEP-1686)— clients must opt in at the initialize handshake.

Tachyon supports both models simultaneously.

The core task system (tasks/list, tasks/get, tasks/cancel, tasks/result) is always available. If a client negotiates the io.modelcontextprotocol/tasks extension, it additionally receives a create_task tool and a task://{id} resource template:

java
1var handle = TachyonServer.builder()
2    .extension(TasksExtension.instance())
3    .port(8080)
4    .start();

Clients that include "io.modelcontextprotocol/tasks" in their initialize capabilities get the extension’s tools and resources automatically. Clients that don’t fall back to standard task endpoints. Tachyon works correctly with clients built against either spec version, today.

Stateless mode for serverless

Session state is a problem on ephemeral infrastructure — AWS Lambda, Cloud Run, or any autoscaling setup where a request may hit a different instance on reconnect. The tradeoff is explicit: stateless mode handles request/response tools and prompts, but gives up session-bound features, like SSE replay via Last-Event-ID. For serverless tool-calling workloads that don’t need those, you trade them for zero distributed session storage and no sticky-routing requirement.

Tachyon’s stateless mode is enabled by default, but you can switch to sessions on:

java
1var handle = TachyonServer.builder()
2    .tool(myTool)
3    .session(s -> s.enabled(true))
4    .port(8080)
5    .start();

Architecture: a non-blocking I/O foundation

AI agent traffic — multiplexed, long-lived SSE sessions and bursty tool calls — is exactly what non-blocking I/O is for. That’s why Tachyon is built on Netty 4.2: native transports (io_uring on Linux, kqueue on macOS, NIO everywhere else), and virtual threads for handler execution so blocking tool implementations stay off the event loop.

No benchmarks yet — I’d rather show numbers than promise them. The work is on the roadmap.

Getting started

Add dev.tachyonmcp:tachyon-server from Maven Central — Apache 2.0, JDK 21+, no framework dependencies.

Or clone and build yourself:

bash
1git clone https://github.com/kpavlov/tachyon.git
2cd tachyon
3mvn install -pl tachyon-server -DskipTests

What’s next

A word on status first: Tachyon is beta, and a solo project moving fast to keep up with the spec. The API will shift before 1.0. It’s a solid choice for experimenting with MCP and staying ahead of the protocol — not yet a production commitment I’d ask you to make blind. With that said, two tracks drive the roadmap.

Spec alignment — full support for the 2026-07-28 draft is the immediate priority: updated protocol version negotiation, and any new SEPs that land. The extension mechanism exists precisely to absorb these changes without touching the core.

Running at scale — rate limiting, HTTP/2, and deeper observability hooks are planned for teams running Tachyon in production.

The source is at github.com/kpavlov/tachyon under the Apache 2.0 licence. If you’re building MCP tooling on the JVM and want to stay ahead of the spec, give Tachyon a try — and open an issue if something doesn’t work the way you expect.

Konstantin Pavlov

Konstantin Pavlov

Software Engineer working with Java, Kotlin, Swift, and AI. Focusing on software architecture and building AI-infused apps. Passionate about testing and Open-Source projects.