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

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

MCP is becoming the REST 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 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.2</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    .bind();

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<ToolResult> {
 2
 3    private static final JsonNode SCHEMA = new ObjectMapper().readTree("""
 4        {
 5          "type": "object",
 6          "properties": {
 7            "city": { "type": "string", "description": "City name" }
 8          },
 9          "required": ["city"]
10        }
11        """);
12
13    GetWeatherTool() {
14        super(ToolDescriptor.builder("get_weather")
15                .description("Get current weather for a city")
16                .inputSchema(SCHEMA)
17                .build());
18    }
19
20    @Override
21    public ToolResult handle(McpContext ctx, Map<String, JsonNode> args) {
22        var city = args.containsKey("city") ? args.get("city").asString() : "";
23        if (city.isBlank()) return ToolResult.error("Parameter `city` is required");
24        return ToolResult.text("🌤️ 22°C in " + city);
25    }
26}

For tools that call downstream APIs or run database queries, extend AbstractAsyncToolHandler and return a CompletionStage:

java
 1class ForecastApiTool extends AbstractAsyncToolHandler<ToolResult> {
 2
 3    private static final JsonNode SCHEMA = /* same schema as above */;
 4
 5    ForecastApiTool() {
 6        super(ToolDescriptor.builder("get_forecast")
 7                .description("Get current weather forecast")
 8                .inputSchema(SCHEMA)
 9                .build());
10    }
11
12    @Override
13    public CompletionStage<ToolResult> handleAsync(McpContext ctx, Map<String, JsonNode> args) {
14        var city = args.containsKey("city") ? args.get("city").asString() : "";
15        if (city.isBlank()) return CompletableFuture.completedFuture(ToolResult.error("city is required"));
16        return weatherApi.fetchAsync(city).thenApply(r -> ToolResult.text(r.summary()));
17    }
18}

Handlers run on virtual threads (JDK 21+), so blocking I/O stays off the event loop without manual thread pool configuration.

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 — form-mode elicitation (SEP-1034, SEP-1330) 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 — 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    .bind();

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. Tachyon’s stateless mode skips session management entirely:

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

Each request is processed independently. 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.

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:

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, pluggable session stores for distributed deployments, 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.