Skip to content

Repository files navigation

hello-mcp-java

ci Java 17 Spring Boot 4.1 licence: MIT

A hello-world Model Context Protocol server in Java, built on Spring Boot and Spring AI, plus a console chat client that drives it with a small local LLM.

It is deliberately small, but it is not a toy. It uses the official Spring AI MCP integration, serves both transports (stdio and streamable HTTP), is covered by 92 automated tests including real protocol round-trips over a real pipe, and handles the things that actually break MCP servers in practice.

New to MCP? Start with GETTING-STARTED.md — it builds this entire project from an empty directory, one step at a time, explaining every dependency and every file.

$ java -jar target/hello-mcp.jar chat

  hello-mcp-chat v1.0.0
  a Model Context Protocol client for Java

Starting the MCP server ...
Server: hello-mcp-server v1.0.0  (4 tools)
Model:  Ollama / phi3:latest / prompt-based tool selection

you> say hello to Kuldeep
  [tool] say_hello {"name":"Kuldeep"}  ->  Hello, Kuldeep! Welcome to the Model Context Protocol.
bot> Hello, Kuldeep! Welcome to the Model Context Protocol.

you> what is 17.5 plus 24.25?
  [tool] add {"a":17.5,"b":24.25}  ->  41.75
bot> The sum of 17.5 and 24.25 is 41.75.

you> who wrote Hamlet?
bot> William Shakespeare wrote "Hamlet", around 1600-1602.

Note the third turn: the model correctly decided not to call a tool.


What is MCP, in one paragraph

An LLM can only talk. The Model Context Protocol is how you let it do things: a server publishes a set of tools (functions with a name, a description and a JSON schema), and a client hands that list to a model. When the model decides a tool would help, the client calls it and feeds the result back. MCP standardises that conversation as JSON-RPC 2.0, so any client can use any server. This repo is one small server plus one small client, so you can see both ends.


Quick start

Prerequisites: JDK 17 or newer. That is all — the Maven Wrapper (mvnw) downloads Maven itself.

git clone https://github.com/kuldeepcodes/hello-mcp-java.git
cd hello-mcp-java

./mvnw clean verify          # build and run all 92 tests   (Windows: .\mvnw.cmd clean verify)
java -jar target/hello-mcp.jar chat

The chat client launches the server itself, so there is nothing else to start.

No LLM installed? It still works:

java -jar target/hello-mcp.jar chat --provider none

That uses deterministic keyword routing instead of a model — enough to prove the MCP wiring end to end. For real conversation, install Ollama and pull a small model:

ollama pull phi3          # ~2.2 GB, works with the prompt planner
ollama pull qwen2.5:1.5b  # ~1 GB, supports native tool calling (more accurate)

The three ways to run it

Command What it does
java -jar target/hello-mcp.jar MCP server over stdio — what local clients spawn
java -jar target/hello-mcp.jar --http MCP server over streamable HTTP on :5099/mcp
java -jar target/hello-mcp.jar chat The chat client, which launches the server itself

Chat client flags:

Flag Default Meaning
--ask "<question>" Ask one question, print the answer, exit
--model <name> phi3 Which Ollama model to use
--provider none auto Skip the LLM entirely and use keyword routing

In-chat commands: /tools, /reset, /help, /exit.


What the server exposes

Tools

Tool Purpose
say_hello Greets someone by name, in any of 10 languages
echo Returns your message unchanged — the simplest possible connectivity check
get_server_time The server's clock, in any IANA time zone
add Adds two numbers exactly, which is precisely what an LLM is bad at

Prompts: friendly_greeting, summarize_capabilities — reusable prompt templates the client can offer.

Resources: hello://server/info and the templated hello://greetings/{language} — read-only data the client can pull in as context.


Using it from a real MCP client

Any MCP client can spawn this server. For VS Code, create .vscode/mcp.json:

{
  "servers": {
    "hello-mcp-java": {
      "type": "stdio",
      "command": "java",
      "args": ["-jar", "/absolute/path/to/hello-mcp-java/target/hello-mcp.jar"]
    }
  }
}

For Claude Desktop, add the same block under mcpServers in claude_desktop_config.json. Use an absolute path to the jar — the client will not run from your project directory.


Three details that break MCP servers

These are the ones worth knowing before you write your own. Each is guarded by a test here.

1. In stdio mode, stdout is the protocol

The client reads JSON-RPC from the server's stdout. One stray System.out.println, one Spring banner, one log line, and the stream is corrupt — the client disconnects with no useful error.

The fix is in logback.xml: every appender targets System.err. Plus bannerMode(OFF) in HelloMcpApplication.

StdioProtocolIT.stdoutIsPureJsonRpc exercises every endpoint, then asserts that every line the server ever wrote to stdout parses as JSON-RPC 2.0. Adding a single println to main makes all ten stdio tests fail.

2. In stdio mode, do not open a socket

If you build on a web application type, Spring binds a port even when the transport is stdio. Start a second copy — as an MCP client legitimately might — and it dies with "address already in use". The stdio branch uses WebApplicationType.NONE.

3. SpringApplicationBuilder.properties() loses to application.yml

properties() registers default properties, which are the lowest-precedence source. A spring.ai.mcp.server.stdio: true line in application.yml silently overrode the transport chosen on the command line: --http bound its port, logged nothing unusual, and answered 404 to every request. HttpProtocolIT now guards this.

A fourth, less dramatic one: the HTTP transport lives in a separate artifact, spring-ai-starter-mcp-server-webflux. Without it you get the same silent 404.


How it works with a model that cannot call tools

Most small local models — phi3 among them — ship without a tool template, so Ollama rejects any request carrying a tools array with HTTP 400. Refusing to run on the hardware people actually have would make this a poor teaching repo, so the client detects the situation and adapts:

Strategy When How
Native tool calling Model supports it Tools are sent in the API request; the model returns a real tool call
Prompt planner Model does not The tool catalogue goes in the prompt; the model replies with a small JSON decision object
Offline routing No model at all Deterministic keyword rules

The strategy is chosen at startup by sending a throwaway request with a tool attached and inspecting the failure. See OllamaClient.probeToolSupport.

One design note worth stealing. get_server_time returns a human field — the local time already written out as "Monday, 24 August 2026 at 3:34 PM". Without it, phi3 read 15:34 and reported "2:32 PM" every single time. Small models are unreliable at converting anything; give them a finished sentence to copy instead.


Project layout

src/main/java/dev/kuldeepcodes/hellomcp/
├── HelloMcpApplication.java        entry point: stdio | --http | chat
├── server/
│   ├── tools/GreetingTools.java    say_hello, echo
│   ├── tools/UtilityTools.java     get_server_time, add
│   ├── prompts/HelloPrompts.java   reusable prompt templates
│   └── resources/ServerResources.java
└── chat/
    ├── ChatRunner.java             console UI, MCP client, the three strategies
    ├── llm/LlmClient.java          the tiny interface a backend must satisfy
    ├── llm/OllamaClient.java       Ollama HTTP API + tool-support probe
    └── agent/
        ├── PromptToolPlanner.java  tool selection for models without tool calling
        ├── JsonExtractor.java      salvages JSON from untidy model output
        └── OfflineToolRouter.java  deterministic fallback

Tests

./mvnw test      # 78 unit tests
./mvnw verify    # + 14 integration tests against the packaged jar

Integration tests use Failsafe, not Surefire, because they launch target/hello-mcp.jar as a child process and so can only run after package. They speak raw JSON-RPC over a real pipe, and they hold the pipe open — writing requests and closing stdin makes the server shut down before it can reply, which is a genuinely confusing way to watch a test fail.


The same project in other languages

This is one of three parallel implementations, same tools, same behaviour, same lessons:


Licence

MIT

About

A hello-world Model Context Protocol server in Java (Spring Boot + Spring AI), with a chat client driven by a small local LLM. Includes a full build-from-scratch guide.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages