Skip to content

Getting Started

This guide will help you build your first MCP server in 5 minutes.

Use this SDK when you want an annotation-driven MCP server in plain Java without a Spring runtime. If you are already building a Spring Boot application, Spring AI MCP is usually the better default; this guide focuses on the lightweight Java path for CLI tools, embedded servers, local automation, and small service processes.

  • Java 17 or later (required by official MCP Java SDK)
<dependency>
<groupId>io.github.thought2code</groupId>
<artifactId>mcp-annotated-java-sdk</artifactId>
<version>0.21.0</version>
</dependency>
implementation 'io.github.thought2code:mcp-annotated-java-sdk:0.21.0'

Create mcp-server.yml in your src/main/resources:

enabled: true
mode: STDIO
name: my-first-mcp-server
version: 1.0.0
type: SYNC
instructions: You are a helpful AI assistant
request-timeout: 20000
capabilities:
resource: true
subscribe-resource: true
prompt: true
tool: true
completion: true
change-notification:
resource: true
prompt: true
tool: true
@McpServerApplication
public class MyFirstMcpServer {
public static void main(String[] args) {
McpApplication.run(MyFirstMcpServer.class, args);
}
}
public class MyResources {
@McpResource(uri = "system://info", description = "System information")
public Map<String, String> getSystemInfo() {
Map<String, String> info = new HashMap<>();
info.put("os", System.getProperty("os.name"));
info.put("java", System.getProperty("java.version"));
info.put("cores", String.valueOf(Runtime.getRuntime().availableProcessors()));
return info;
}
}
public class MyTools {
@McpTool(description = "Calculate the sum of two numbers")
public int add(
@McpToolParam(name = "a", description = "First number") int a,
@McpToolParam(name = "b", description = "Second number") int b
) {
return a + b;
}
}
public class MyPrompts {
@McpPrompt(description = "Generate code for a given task")
public String generateCode(
@McpPromptParam(name = "language", description = "Programming language") String language,
@McpPromptParam(name = "task", description = "Task description") String task
) {
return String.format("Write %s code to: %s", language, task);
}
}
Terminal window
# Compile your project
./mvnw clean package

Run MyFirstMcpServer from your IDE, or use java -cp ... with your compiled classes and dependencies on the classpath. Use an executable JAR setup in your own project if you need java -jar with a single file.

To load a non-default configuration file name, use McpApplication.run(MyFirstMcpServer.class, args, "custom-mcp-server.yml").

For deployment, package your application as an executable fat JAR so the MCP Java SDK, this SDK, and all transitive dependencies are available at runtime. Keep mcp-server.yml under src/main/resources so it is included on the runtime classpath.

With Maven Shade, configure the JAR manifest main class:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.2</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>com.example.MyFirstMcpServer</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>

Then run:

Terminal window
java -jar target/your-app.jar

If you use Gradle Shadow, configure the main class in the manifest:

tasks.shadowJar {
manifest {
attributes 'Main-Class': 'com.example.MyFirstMcpServer'
}
}

This SDK supports two MCP server modes. The mode field is required in mcp-server.yml; there is no implicit default when loading from YAML (defaults only apply when building a configuration programmatically via ServerConfiguration.builder()).

Based on standard input/output communication, suitable for CLI tools and local development.

mcp-server.yml
mode: STDIO

HTTP streaming for web applications, recommended for production.

mcp-server.yml
mode: STREAMABLE
streamable:
mcp-endpoint: /mcp/message
disallow-delete: false
keep-alive-interval: 20000
port: 8080

STREAMABLE is the supported HTTP transport.

The core fields and applicable nested settings below are required when loading configuration from YAML; omitting a required field causes the server to fail startup with Missing config key '...'. Conditional fields are required only when their corresponding feature or transport is enabled. The “Builder default” column lists the value applied only when constructing a configuration programmatically via ServerConfiguration.builder().

Property Description Builder default
enabled Enable/disable MCP server true
mode Server mode: STDIO, STREAMABLE STREAMABLE
name Server name mcp-server
version Server version 1.0.0
type Server type: SYNC, ASYNC SYNC
instructions Instructions for the LLM client (empty string)
request-timeout Request timeout in milliseconds 20000
capabilities.resource Enable resource support true
capabilities.subscribe-resource Enable resource subscription true
capabilities.prompt Enable prompt support true
capabilities.tool Enable tool support true
capabilities.completion Enable completion support true
change-notification.resource Notify clients on resource change true
change-notification.prompt Notify clients on prompt change true
change-notification.tool Notify clients on tool change true
streamable.mcp-endpoint Streamable HTTP MCP path /mcp/message
streamable.disallow-delete Reject HTTP DELETE on session false
streamable.keep-alive-interval Keep-alive interval (ms) 20000
streamable.port HTTP port for STREAMABLE mode 8080

Conditional requirements:

  • capabilities.subscribe-resource is required only when capabilities.resource is true.
  • streamable.* fields are required only when mode is STREAMABLE. When mode is STDIO, the streamable section is ignored and may be omitted.

The type property selects the MCP Java SDK server API (SYNC or ASYNC). It does not make your annotated methods reactive.

  • SYNC — methods run on the request thread.
  • ASYNC — the SDK exposes async handlers that wrap your method in Mono.fromCallable(...). Your code is still blocking Java; you do not return Mono from @McpTool / @McpPrompt / @McpResource methods.

Use SYNC by default. Choose ASYNC only when your deployment requires the async MCP server API. Long work still blocks a Reactor worker thread under ASYNC.

The SDK creates one instance per component class (via a public no-arg constructor) and reuses it for all requests. Concurrent MCP calls share that object. Keep components stateless or thread-safe; avoid unsynchronized per-request instance fields.

Set profile in the base file to load mcp-server-{profile}.yml from the classpath. Profile values are merged into the base configuration with Jackson deep merge; nested objects such as capabilities and streamable are merged field-by-field. The profile name always comes from the base file. After merge, transport settings that do not match the resolved mode are cleared (for example, streamable is removed when mode is STDIO).

You can use profiles for different environments:

# mcp-server.yml (base configuration)
enabled: true
mode: STREAMABLE
name: my-mcp-server
version: 1.0.0
profile: dev
# mcp-server-dev.yml (profile-specific configuration)
streamable:
port: 8080

The typical project structure is as follows:

your-mcp-project/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ ├── MyMcpServer.java # Main entry point
│ │ │ ├── components/
│ │ │ │ ├── MyResources.java # MCP Resources
│ │ │ │ ├── MyTools.java # MCP Tools
│ │ │ │ └── MyPrompts.java # MCP Prompts
│ │ │ └── service/
│ │ │ └── BusinessLogic.java # Business logic
│ │ └── resources/
│ │ └── mcp-server.yml # MCP configuration
│ └── test/
│ └── java/
│ └── com/
│ └── example/
│ └── McpServerTest.java # Unit tests
└── target/
└── *.jar # Build output (name depends on your project)