MCP Directory

How to Build an MCP Server in Java

Two real paths to a Java MCP server — the raw official SDK and Spring AI — plus the transport and tool-count decisions that actually bite.

Hua·June 30, 2026·6 min read
Detailed macro shot of electronic circuit board showing microchips and components.
Photo by Jakub Pabis on Pexels

To build an MCP server in Java you have two sane choices: the official io.modelcontextprotocol SDK for full control, or Spring AI's MCP server starter if your app already runs on Spring Boot. Both speak the same protocol, both let you expose Java methods as tools, and both default to stdio — the transport about 90% of servers in this directory run over. Below is what each path looks like, when to pick which, and the decisions that trip people up.

If you've never wired a server into a client before, skim how to add an MCP server first — the second half of this piece assumes you know where the client config file lives.

Pick your path: raw SDK vs Spring AI

Use the official Java SDK when you want a small, dependency-light process and control over the lifecycle; use Spring AI when the server is part of an existing Spring Boot app and you want tools discovered by annotation. The protocol on the wire is identical, so this is an ergonomics call, not a capability one.

Official Java SDKSpring AI MCP Server
Best forStandalone servers, minimal depsExisting Spring Boot apps
Tool definitionRegister SyncToolSpecification@Tool on a bean method
Transport setupYou choose stdio/HTTP explicitlyStarter picks it from config
Boot footprintTiny, fast cold startFull Spring context
Escape hatchYou own everythingDrop to the SDK when needed

My default: standalone tool? Raw SDK. Exposing capabilities that already live inside a Spring service? Spring AI, because you're not going to re-plumb your beans just to satisfy a protocol.

Minimal server with the official SDK

Add the SDK dependency, then register one tool and start a stdio transport — that's the whole server. The core object is McpServer; each tool is a name, a JSON input schema, and a handler that returns content.

<dependency>
  <groupId>io.modelcontextprotocol.sdk</groupId>
  <artifactId>mcp</artifactId>
</dependency>
var transport = new StdioServerTransportProvider();

var echo = new McpServerFeatures.SyncToolSpecification(
    new Tool("echo", "Echo back the input",
        "{\"type\":\"object\",\"properties\":\n           {\"text\":{\"type\":\"string\"}}}"),
    (exchange, args) -> new CallToolResult(
        List.of(new TextContent((String) args.get("text"))), false));

McpServer.sync(transport)
    .serverInfo("java-demo", "0.1.0")
    .tools(echo)
    .build();

Two things people get wrong here. First, never write logging to stdout — stdio uses standard out for the JSON-RPC frames, so a stray System.out.println corrupts the stream. Route logs to stderr or a file. Second, the input schema is the contract the model sees; a vague schema produces vague tool calls. Spend more effort there than on the handler.

The Spring AI path

With Spring AI, add the MCP server starter and annotate a method with @Tool — component scanning registers it, and the starter wires the transport from application.properties. This is the least-code option if you're already in Spring.

<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>
@Service
class WeatherTools {
  @Tool(description = "Get the current temperature for a city")
  String temperature(String city) {
    return lookup(city); // your logic
  }
}

Spring AI infers the JSON schema from the method signature, which is convenient but worth double-checking — inferred schemas are only as clear as your parameter names. city is fine; arg0 is not.

stdio or HTTP? Choose the transport deliberately

Default to stdio; reach for HTTP (streamable HTTP / SSE) only when the server must run as a shared, network-reachable service. Local, single-user tools should stay on stdio — it's simpler, needs no auth layer, and matches how the vast majority of the ecosystem actually ships.

  • stdio — client launches your JAR as a subprocess. No ports, no auth, no CORS. This is the right call for anything that touches the local machine, and it's what the official Filesystem reference server uses.
  • HTTP — the server runs independently and clients connect over the network. Now authentication, transport security, and access control are your problem. Read what actually matters for MCP security before you expose a write-capable tool this way — the trade-offs are real. The GitHub MCP server ships both a local Docker mode and a hosted remote for exactly this reason.

If you're genuinely unsure, ship stdio first. Promoting a working stdio server to HTTP later is a transport swap; the tool code doesn't change.

Keep the tool count honest

Expose the fewest tools that do the job — most clients degrade past roughly 40 active tools, and that budget is shared across every server the user has installed, not yours alone. A Java server that dumps 30 tools into the session is antisocial even if each one works.

The pattern that scales: a handful of broad, well-described tools beats a wide grid of narrow ones. The median server in this directory exposes about 10 tools; that's a good ceiling to aim for. If your Spring service has 40 public methods, resist annotating all of them — pick the ones a model should actually call. When you're wiring several servers together, the best MCP servers list and our config generator both help you stay under the ceiling.

Wire it into a client and test

Point your client's config at the JAR and confirm the tools appear before you trust them. For a stdio server, the client config runs java -jar and talks to the process over stdin/stdout:

{
  "mcpServers": {
    "java-demo": {
      "command": "java",
      "args": ["-jar", "/abs/path/target/java-demo.jar"]
    }
  }
}

Use an absolute path to the JAR — relative paths resolve against the client's working directory, not yours, and that's the number-one reason a freshly built server "doesn't show up." If the tool list is empty or a call hangs, the troubleshooting guide covers the usual stdio suspects (stdout pollution, wrong path, non-executable JAR). Want the model to write against a real, current API while you build? Point it at Context7 so it pulls version-specific docs instead of guessing.

FAQ

Do I need Spring to build an MCP server in Java?

No. The official `io.modelcontextprotocol` Java SDK is standalone and has no framework dependency — you register tools and start a transport in plain Java. Spring AI's MCP starter is only worth it if your app already runs on Spring Boot and you want tools discovered by `@Tool` annotation.

Is the official Java MCP SDK free to use?

Yes. Both the official MCP Java SDK and Spring AI are open source and free. The only costs come from whatever your tools call — a paid API, cloud infrastructure for an HTTP deployment, and so on.

stdio or HTTP for a Java MCP server?

Use stdio unless the server must be a shared network service. stdio needs no ports, auth, or CORS and matches how roughly 90% of servers run. Switch to streamable HTTP/SSE only when multiple clients connect over the network — and add authentication before exposing any write-capable tool.

Why doesn't my Java server's tools show up in the client?

Usually one of three things: a relative JAR path (use an absolute path in the client config), logging written to stdout instead of stderr (it corrupts the JSON-RPC stream on stdio), or a JAR that wasn't rebuilt. Check the client's server logs and the troubleshooting guide for the exact error.

How many tools should a Java MCP server expose?

Aim for around 10 or fewer. Most clients degrade past ~40 active tools, and that budget is shared across every server the user installs — so a lean set of broad, well-described tools beats a wide grid of narrow ones.

Put this into practice

Browse MCP servers by capability, or check your own setup's tool budget and security.

More essays