MCP
Model Context Protocol (MCP) module for Jooby.
The MCP module provides seamless integration with the Model Context Protocol, allowing your application to act as a standardized AI context server. It automatically bridges your Java/Kotlin methods with LLM clients by exposing them as Tools, Resources, and Prompts.
Usage
1) Add the dependencies (Jooby MCP, Jackson, and the APT processor):
<!-- Jackson Module-->
<dependency>
<groupId>io.jooby</groupId>
<artifactId>jooby-jackson3</artifactId>
<version>4.2.0</version>
</dependency>
<!-- MCP Jackson Module-->
<dependency>
<groupId>io.jooby</groupId>
<artifactId>jooby-mcp-jackson3</artifactId>
<version>4.2.0</version>
</dependency>
<!-- MCP Module-->
<dependency>
<groupId>io.jooby</groupId>
<artifactId>jooby-mcp</artifactId>
<version>4.2.0</version>
</dependency>
|
Note
|
You must configure the Jooby Annotation Processor (APT) in your build. The MCP module relies on APT to generate high-performance routing code with zero reflection overhead. |
2) Define a service and expose capabilities via annotations:
import io.jooby.annotation.mcp.McpTool;
public class CalculatorService {
@McpTool(description = "Adds two numbers together")
public int add(int a, int b) {
return a + b;
}
}
3) Install the module using Jackson and the generated service:
import io.jooby.jackson3.Jackson3Module;
import io.jooby.mcp.jackson3.McpJackson3Module;
import io.jooby.mcp.McpModule;
{
install(new Jackson3Module()); (1)
install(new McpJackson3Module()); (2)
install(new McpModule(new CalculatorServiceMcp_())); (3)
}
-
Install JSON support (Jackson is required for MCP JSON-RPC serialization). For Jackson 2, use
JacksonModule()instead. -
Install MCP JSON support. For Jackson 2, use
McpJackson2Module()instead. -
Install the MCP module with the APT-generated
McpServicedispatcher. The generated class always ends with theMcp_suffix.
Core Capabilities
The module uses annotations to expose application logic to AI clients:
-
@McpTool: Exposes a method as an executable tool. The module automatically generates JSON schemas for the parameters. -
@McpPrompt: Exposes a method as a reusable prompt template. -
@McpResource/@McpResourceTemplate: Exposes static or dynamic data as an MCP resource (e.g.,file://config). -
@McpCompletion: Handles auto-completion logic for prompts and resources.
Schema Descriptions & Javadoc
Rich descriptions are highly recommended for LLM usage so the model understands exactly what a tool, prompt, or resource does. You can provide these descriptions directly in the MCP annotations (e.g., @McpTool(description = "…")).
However, if you omit them, the Jooby Annotation Processor will automatically extract descriptions from your standard Javadoc comments, including method descriptions and @param tags.
import io.jooby.annotation.mcp.McpTool;
public class WeatherService {
/**
* Retrieves the current weather forecast for a given location.
*
* @param location The city and state, e.g., "San Francisco, CA"
* @param units The temperature unit to use (celsius or fahrenheit)
*/
@McpTool
public WeatherForecast getWeather(String location, String units) {
// ...
}
}
In the example above, the LLM will automatically receive the method’s Javadoc summary as the tool description, and the @param comments as the descriptions for the location and units JSON schema properties.
Transports
By default, the MCP module starts a single server using the STREAMABLE_HTTP transport. You can easily switch to other supported transports such as SSE (Server-Sent Events), WEBSOCKET, or STATELESS_STREAMABLE_HTTP.
import io.jooby.mcp.McpModule.Transport;
{
install(new McpModule(new CalculatorServiceMcp_())
.transport(Transport.WEBSOCKET));
}
Output Schema Generation
By default, the framework does not generate JSON output schemas for tools in order to save LLM context window tokens. You can enable it globally on the module, or override it per-method using the @McpOutputSchema annotation.
{
// Enable output schema generation for all tools by default
install(new McpModule(new CalculatorServiceMcp_())
.generateOutputSchema(true));
}
Alternatively, you can control output schema generation using your application configuration properties. Configuration properties always take precedence over the programmatic setup, and allow you to configure behavior per-server.
# Global fallback for all servers
mcp.generateOutputSchema = true
# Per-server override (takes precedence over the global flag)
mcp.calculator.generateOutputSchema = false
You can explicitly override the global/config flag and bypass Java type erasure using nested @McpOutputSchema annotations:
import io.jooby.annotation.mcp.McpTool;
import io.jooby.annotation.mcp.McpOutputSchema;
public class UserService {
@McpTool
@McpOutputSchema.ArrayOf(User.class) (1)
public List<Object> findUsers(String query) {
// ...
}
@McpTool
@McpOutputSchema.Off (2)
public HugeDataset getBigData() {
// ...
}
}
-
Forces array schema generation for
User, overriding genericObjecterasure and the global/config flag. -
Explicitly disables schema generation for this specific tool.
Custom Invokers & Telemetry
You can inject custom logic (like SLF4J MDC context propagation, tracing, or custom error handling) around every tool, prompt, or resource execution by providing an McpInvoker.
|
Note
|
Invokers are chained. You can register multiple invokers and they will wrap the execution in the order they were added:
|
import io.jooby.mcp.McpInvoker;
import io.jooby.mcp.McpOperation;
import org.slf4j.MDC;
public class MdcMcpInvoker implements McpInvoker {
@Override
public <R> R invoke(McpOperation operation, SneakyThrows.Supplier<R> action) {
try {
MDC.put("mcp.id", operation.id()); (1)
MDC.put("mcp.class", operation.className());
MDC.put("mcp.method", operation.methodName());
return action.get(); (2)
} finally {
MDC.remove("mcp.id");
MDC.remove("mcp.class");
MDC.remove("mcp.method");
}
}
}
{
install(new McpModule(new CalculatorServiceMcp_())
.invoker(new MdcMcpInvoker())); (3)
}
-
Extract rich contextual data from the
McpOperationrecord. -
Proceed with the execution chain.
-
Register the invoker. Jooby will safely map any business exceptions thrown by your action into valid MCP JSON-RPC errors.
Multiple Servers
You can run multiple, completely isolated MCP server instances within the same Jooby application by utilizing the @McpServer("serverName") annotation on your service classes.
When bootstrapping multiple servers, you must provide configuration for each server in your application.conf.
mcp.default.name = "default-mcp-server"
mcp.default.version = "1.0.0"
mcp.calculator.name = "calculator-mcp-server"
mcp.calculator.version = "1.0.0"
mcp.calculator.transport = "sse"
mcp.calculator.mcpEndpoint = "/mcp/calculator/sse"
{
// Bootstraps services each on their corresponding service base based on the @McpServer mappings
install(new McpModule(
new DefaultServiceMcp_(),
new CalculatorServiceMcp_()
));
}
Debugging & Testing (MCP Inspector)
When building an MCP server, it is highly recommended to test your tools, prompts, and resources locally before connecting them to a real LLM client.
The McpInspectorModule provides a built-in, interactive web UI that acts as a dummy LLM client. It allows you to manually trigger tools, view generated output schemas, inspect resources, and debug JSON-RPC payloads in real-time.
1) Add the inspector dependency (typically as a test or development dependency):
<!-- MCP Inspector-->
<dependency>
<groupId>io.jooby</groupId>
<artifactId>jooby-mcp-inspector</artifactId>
<version>4.2.0</version>
</dependency>
2) Install the module, ensuring it is only active during development:
import io.jooby.mcp.McpModule;
import io.jooby.mcp.inspector.McpInspectorModule;
{
install(new McpModule(
new DefaultServiceMcp_(),
new CalculatorServiceMcp_()
));
// Only enable the inspector UI in the 'dev' environment
if (getEnvironment().isActive("dev")) {
install(new McpInspectorModule());
}
}
Once the application starts, open your browser and navigate to the default inspector route: http://localhost:8080/mcp-inspector.
Inspector Configuration
You can customize the behavior and mounting point of the Inspector UI using its programmatic builder methods:
{
if (getEnvironment().isActive("dev")) {
install(new McpInspectorModule()
.path("/debug/mcp") (1)
.defaultServer("calculator-mcp-server") (2)
.autoConnect(true) (3)
);
}
}
-
Changes the base path for the Inspector UI (defaults to
/mcp-inspector). -
Automatically selects a specific named server in the UI dropdown when dealing with multiple MCP servers.
-
Automatically connects to the selected server as soon as the page loads.
Special Thanks
A special thanks to kliushnichenko. This MCP module was heavily inspired by and based upon their foundational work and contributions to the ecosystem.