Jooby

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):

Maven
Gradle
<!-- 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:

Java
Kotlin
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:

Java
Kotlin
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)
}
  1. Install JSON support (Jackson is required for MCP JSON-RPC serialization). For Jackson 2, use JacksonModule() instead.

  2. Install MCP JSON support. For Jackson 2, use McpJackson2Module() instead.

  3. Install the MCP module with the APT-generated McpService dispatcher. The generated class always ends with the Mcp_ 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.

Javadoc Extraction Example
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.

Changing Transport
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.

Programmatic Global Configuration
{
   // 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.

application.conf
# 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:

Per-Method Override
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() {
    // ...
  }
}
  1. Forces array schema generation for User, overriding generic Object erasure and the global/config flag.

  2. 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: install(new McpModule(…​).invoker(new LoggingInvoker()).invoker(new SecurityInvoker()));

MDC Invoker Example
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)
}
  1. Extract rich contextual data from the McpOperation record.

  2. Proceed with the execution chain.

  3. 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.

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"
Java Bootstrap
{
  // 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):

Maven
Gradle
<!-- 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:

Java Bootstrap
Java
Kotlin
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:

Programmatic Setup
Java
Kotlin
{
  if (getEnvironment().isActive("dev")) {
    install(new McpInspectorModule()
        .path("/debug/mcp")                           (1)
        .defaultServer("calculator-mcp-server")       (2)
        .autoConnect(true)                            (3)
    );
  }
}
  1. Changes the base path for the Inspector UI (defaults to /mcp-inspector).

  2. Automatically selects a specific named server in the UI dropdown when dealing with multiple MCP servers.

  3. 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.