MCP Tutorial
In this tutorial, you will:
- build a simple MCP server in Java
- expose a tool (
add_numbers) - build the project in IntelliJ
- connect it to Codex and Claude CLI
- call it from natural language
MCP (Model Context Protocol) lets AI tools like Codex and Claude:
call your code as tools
Think of it like:
- REST → for frontend
- MCP → for AI
Create a new Maven project in IntelliJ:
- Open IntelliJ → New Project
- Choose Maven
- Set:
- GroupId:
demo - ArtifactId:
mcp-demo
- GroupId:
- Click Create
Edit your pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>app</groupId>
<artifactId>mcp-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-bom</artifactId>
<version>1.1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-json-jackson3</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>app.McpServerApp</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Create a file:
src/main/java/demo/McpServerApp.java
Add this code:
package app;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.transport.StdioServerTransportProvider;
import io.modelcontextprotocol.json.jackson3.JacksonMcpJsonMapper;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
import io.modelcontextprotocol.spec.McpSchema.TextContent;
import io.modelcontextprotocol.spec.McpSchema.Tool;
import tools.jackson.databind.json.JsonMapper;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class McpServerApp {
private static final String SERVER_NAME = "demo-mcp-server";
private static final String SERVER_VERSION = "1.0.0";
private static final String ADD_NUMBERS_SCHEMA = """
{
"type": "object",
"properties": {
"a": { "type": "integer" },
"b": { "type": "integer" }
},
"required": ["a", "b"]
}
""";
private static final String CURRENT_TIME_SCHEMA = """
{
"type": "object",
"properties": {},
"additionalProperties": false
}
""";
public static void main(String[] args) {
var jsonMapper = new JacksonMcpJsonMapper(JsonMapper.builder().build());
var transportProvider = new StdioServerTransportProvider(jsonMapper);
var server = McpServer.sync(transportProvider)
.serverInfo(SERVER_NAME, SERVER_VERSION)
.capabilities(ServerCapabilities.builder()
.tools(true)
.build())
.toolCall(addNumbersTool(jsonMapper), (exchange, request) -> {
int a = (int) request.arguments().get("a");
int b = (int) request.arguments().get("b");
return textResult("Result: " + (a + b));
})
.toolCall(currentTimeTool(jsonMapper), (exchange, request) ->
textResult(ZonedDateTime.now().format(DateTimeFormatter.ISO_ZONED_DATE_TIME)))
.build();
Runtime.getRuntime().addShutdownHook(new Thread(server::close));
awaitShutdown(server::close);
}
private static Tool addNumbersTool(JacksonMcpJsonMapper jsonMapper) {
return Tool.builder()
.name("add_numbers")
.description("Add two numbers")
.inputSchema(jsonMapper, ADD_NUMBERS_SCHEMA)
.build();
}
private static Tool currentTimeTool(JacksonMcpJsonMapper jsonMapper) {
return Tool.builder()
.name("current_time")
.description("Return the current server time in ISO-8601 format")
.inputSchema(jsonMapper, CURRENT_TIME_SCHEMA)
.build();
}
private static CallToolResult textResult(String text) {
return CallToolResult.builder()
.content(List.of(new TextContent(text)))
.build();
}
private static void awaitShutdown(Runnable closeServer) {
try {
new CountDownLatch(1).await();
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
closeServer.run();
}
}
}
This is the newer server style in the official Java SDK:
- create a
StdioServerTransportProvider - build a sync server with
McpServer.sync(...) - register tools with
toolCall(...) - define tool input with JSON Schema
STDIO = standard input/output
👉 Instead of HTTP, the AI talks to your program directly via:
- input stream
- output stream
✔ simplest setup ✔ perfect for local tools ✔ still the default way to connect small local MCP servers to CLI tools
Use IntelliJ (not terminal):
- Open the Maven tool window (right side)
- Expand your project → Lifecycle
- Double-click clean
- Double-click package
You should now have a jar in:
target/mcp-demo-1.0-SNAPSHOT.jar
Run:
codex mcp add demo-server -- java -jar /Users/jobe/Documents/dat4/mcp-demo/target/mcp-demo-1.0-SNAPSHOT.jar
👉 Replace with your actual absolute path!
Run:
claude mcp add demo-server \
--command java \
--arg -jar \
--arg /FULL/PATH/TO/target/mcp-demo-1.0-SNAPSHOT.jar
👉 Use the same jar path as above.
Codex:
codex mcp list
Claude CLI:
claude mcp list
You should see demo-server in both outputs.
In Codex, try:
Use the add_numbers tool with a=5 and b=7
In Claude CLI, try:
Use the add_numbers tool with a=5 and b=7
👉 Both clients will:
- detect your tool
- call your Java server
- return the result
You built:
AI → MCP → your Java code
Instead of:
Frontend → REST → backend
MCP is just another interface to your backend logic
- Where does the logic live?
- What is the difference between MCP and REST?
- Why might AI tools need this instead of HTTP APIs?
- Did you run
codex mcp addorclaude mcp add? - Did you restart the client?
- Is the
.jarpath correct? - Does
java -jar ...run manually?
- Check for missing dependencies
- Run manually to debug:
java -jar target/mcp-demo-1.0-SNAPSHOT.jar
You can now:
- connect MCP to real services (database, APIs)
- combine with Javalin
- build AI-powered backend features