Skip to content

Commit 8d53bbf

Browse files
committed
- add javadoc to public classes
1 parent f628c70 commit 8d53bbf

5 files changed

Lines changed: 354 additions & 48 deletions

File tree

modules/jooby-mcp/src/main/java/io/jooby/mcp/McpInvoker.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,73 @@
77

88
import io.jooby.SneakyThrows;
99

10+
/**
11+
* Intercepts and wraps the execution of MCP (Model Context Protocol) operations, such as tools,
12+
* prompts, resources, and completions.
13+
*
14+
* <p>The {@link McpInvoker} acts as a middleware or decorator around the generated MCP routing
15+
* logic. It allows you to seamlessly inject cross-cutting concerns—such as telemetry, logging
16+
* (SLF4J MDC), transaction management, or custom error handling—right before and after an operation
17+
* executes.
18+
*
19+
* <h2>Chaining Invokers</h2>
20+
*
21+
* <p>Jooby provides a default internal invoker that gracefully maps standard framework exceptions
22+
* to MCP JSON-RPC errors. When you register a custom invoker via {@link
23+
* io.jooby.mcp.McpModule#invoker(McpInvoker)}, the framework automatically chains your custom
24+
* invoker with the default one using the {@link #then(McpInvoker)} method.
25+
*
26+
* <h3>Example: MDC Context Propagation</h3>
27+
*
28+
* <pre>{@code
29+
* public class MdcMcpInvoker implements McpInvoker {
30+
* public <R> R invoke(String operationId, SneakyThrows.Supplier<R> action) {
31+
* try {
32+
* MDC.put("mcp.operation", operationId);
33+
* // Execute the actual tool or proceed to the next invoker in the chain
34+
* return action.get();
35+
* } finally {
36+
* MDC.remove("mcp.operation");
37+
* }
38+
* }
39+
* }
40+
* * // Register and automatically chain it:
41+
* install(new McpModule(new MyServiceMcp_())
42+
* .invoker(new MdcMcpInvoker()));
43+
* }</pre>
44+
*
45+
* @author edgar
46+
* @since 4.2.0
47+
*/
1048
public interface McpInvoker {
1149

50+
/**
51+
* Executes the given MCP operation.
52+
*
53+
* @param operationId The identifier of the operation being executed. Typically formatted as
54+
* {@code [type]/[name]} (e.g., {@code "tools/add_numbers"}, {@code "prompts/greeting"}, or
55+
* {@code "resources/file://config"}).
56+
* @param action The actual execution of the operation, or the next invoker in the chain. Must be
57+
* invoked via {@link SneakyThrows.Supplier#get()} to proceed.
58+
* @param <R> The return type of the operation.
59+
* @return The result of the operation.
60+
*/
1261
<R> R invoke(String operationId, SneakyThrows.Supplier<R> action);
1362

1463
/**
1564
* Chains this invoker with another one. This invoker runs first, and its "action" becomes calling
1665
* the next invoker.
1766
*
67+
* <p>This is used internally by {@link io.jooby.mcp.McpModule} to compose user-provided invokers
68+
* with the default framework exception mappers.
69+
*
1870
* @param next The next invoker in the chain.
1971
* @return A composed invoker.
2072
*/
2173
default McpInvoker then(McpInvoker next) {
74+
if (next == null) {
75+
return this;
76+
}
2277
return new McpInvoker() {
2378
@Override
2479
public <R> R invoke(String operationId, SneakyThrows.Supplier<R> action) {

modules/jooby-mcp/src/main/java/io/jooby/mcp/McpModule.java

Lines changed: 70 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -34,85 +34,108 @@
3434
/**
3535
* MCP (Model Context Protocol) module for Jooby.
3636
*
37-
* <p>The MCP module provides integration with the Model Context Protocol server, enabling
38-
* standardized communication between clients and servers. It allows applications to:
37+
* <p>The MCP module provides seamless integration with the Model Context Protocol, allowing your
38+
* application to act as a standardized AI context server. It automatically bridges your Java/Kotlin
39+
* methods with LLM clients by exposing them as Tools, Resources, and Prompts.
40+
*
41+
* <h2>Key Features</h2>
3942
*
4043
* <ul>
41-
* <li>Expose server capabilities as tools, resources, and prompts
42-
* <li>Handle client connections and sessions via SSE
43-
* <li>Process protocol messages and events
44-
* <li>Manage server capabilities and tool specifications
44+
* <li><b>Compile-Time Discovery:</b> Automatically generates routing logic for {@code @McpTool},
45+
* {@code @McpPrompt}, and {@code @McpResource} annotations with zero reflection overhead via
46+
* APT.
47+
* <li><b>Rich Schema Generation:</b> Tool and parameter descriptions are extracted directly from
48+
* your MCP annotations, gracefully falling back to standard JavaDoc comments if omitted.
49+
* <li><b>Transport Flexibility:</b> Supports {@link Transport#STREAMABLE_HTTP} (default), {@link
50+
* Transport#SSE}, {@link Transport#WEBSOCKET}, and {@link
51+
* Transport#STATELESS_STREAMABLE_HTTP}.
52+
* <li><b>Execution Interception:</b> Chain custom {@link McpInvoker} instances to seamlessly
53+
* inject MDC context, telemetry, or custom error handling around executions.
54+
* <li><b>LLM Self-Healing:</b> Automatically catches internal business exceptions and translates
55+
* them into valid MCP error payloads, allowing LLMs to auto-correct their own mistakes.
4556
* </ul>
4657
*
47-
* <h2>Usage</h2>
58+
* <h2>Basic Usage</h2>
59+
*
60+
* <p>By default, the module requires zero configuration in {@code application.conf} and will spin
61+
* up a single {@code streamable-http} server.
4862
*
49-
* <p>Add the module to your application:
63+
* <p>The module relies on Jackson for JSON-RPC message serialization. Here is the standard setup
64+
* using Jackson 3:
5065
*
5166
* <pre>{@code
5267
* {
53-
* install(new JacksonModule());
54-
* install(new McpModule(new DefaultMcpServer()));
68+
* // 1. Install Jackson 3 support
69+
* install(new Jackson3Module());
70+
* install(new McpJackson3Module());
71+
* // 2. Install the MCP module with your APT-generated McpService
72+
* install(new McpModule(new MyServiceMcp_()));
5573
* }
5674
* }</pre>
5775
*
58-
* <h2>Configuration</h2>
76+
* <i>Note: If your project still uses Jackson 2, simply swap the modules to {@code install(new
77+
* JacksonModule());} and {@code install(new McpJackson2Module());}.</i>
5978
*
60-
* <p>The module requires the following configuration in your application.conf:
79+
* <h2>Changing the Default Transport</h2>
80+
*
81+
* <p>If you want to use a different transport protocol for the default server, you can configure it
82+
* directly in the Java DSL:
6183
*
6284
* <pre>{@code
63-
* mcp.default {
64-
* name: "my-awesome-mcp-server" # Required
65-
* version: "0.0.1" # Required
66-
* sseEndpoint: "/mcp/sse" # Optional (default: /mcp/sse)
67-
* messageEndpoint: "/mcp/message" # Optional (default: /mcp/message)
85+
* {
86+
* install(new McpModule(new MyServiceMcp_())
87+
* .transport(Transport.SSE)); // Or Transport.WEBSOCKET, Transport.STATELESS_STREAMABLE_HTTP
6888
* }
6989
* }</pre>
7090
*
71-
* <h2>Features</h2>
91+
* <h2>Custom Invokers & Telemetry</h2>
7292
*
73-
* <ul>
74-
* <li>MCP server implementation with SSE transport
75-
* <li>Tools Auto-discovery at build time
76-
* <li>Server capabilities configuration
77-
* <li>Configurable endpoints
78-
* <li>Multiple servers support
79-
* </ul>
93+
* <p>You can inject custom logic (like SLF4J MDC context propagation or Tracing spans) around every
94+
* tool, prompt, or resource call by providing a custom {@link McpInvoker}:
95+
*
96+
* <pre>{@code
97+
* {
98+
* install(new McpModule(new MyServiceMcp_())
99+
* .invoker(new MyCustomMdcInvoker())); // Chains automatically with the Default Exception Mapper
100+
* }
101+
* }</pre>
102+
*
103+
* <h2>Multiple Servers</h2>
80104
*
81-
* <h2>Multiple servers</h2>
105+
* <p>The generated {@link McpService} instances do not represent servers themselves; they are
106+
* mapped to specific server instances using the {@code @McpServer("serverKey")} annotation on your
107+
* original class.
82108
*
83-
* <p>To run multiple MCP server instances in the same application, use a @McpServer("calculator")
84-
* annotation:
109+
* <p>If you route services to multiple, isolated servers, you <b>must</b> define their specific
110+
* configurations in your {@code application.conf}:
85111
*
86112
* <pre>{@code
87113
* {
88-
*
89-
* install(new JacksonModule());
90-
* install(new McpModule(new DefaultMcpServer(), new CalculatorMcpServer()));
114+
* // Jooby will boot two separate MCP servers based on the @McpServer mapping of these services
115+
* install(new McpModule(new DefaultServiceMcp_(), new CalculatorServiceMcp_()));
91116
* }
92117
* }</pre>
93118
*
94-
* <p>Each instance requires its own configuration block:
119+
* <p>{@code application.conf}:
95120
*
96121
* <pre>{@code
97-
* mcp {
98-
* default {
99-
* name: "default-mcp-server"
100-
* version: "1.0.0"
101-
* sseEndpoint: "/mcp/sse"
102-
* messageEndpoint: "/mcp/message"
103-
* }
104-
* calculator {
105-
* name: "calculator-mcp-server"
106-
* version: "1.0.0"
107-
* sseEndpoint: "/mcp/calculator/sse"
108-
* messageEndpoint: "/mcp/calculator/message"
109-
* }
122+
* mcp.calculator {
123+
* name: "calculator-mcp-server"
124+
* version: "1.0.0"
125+
* transport: "web-socket"
126+
* mcpEndpoint: "/mcp/calculator/ws"
110127
* }
111-
*
112128
* }</pre>
113129
*
130+
* <h2>Testing and Debugging</h2>
131+
*
132+
* <p>For local development, Jooby provides a built-in UI to test your AI capabilities. Simply
133+
* install the {@link McpInspectorModule} alongside this module to interactively execute your tools,
134+
* prompts, and resources straight from your browser.
135+
*
114136
* @author kliushnichenko
115-
* @since 1.0.0
137+
* @author edgar
138+
* @since 4.2.0
116139
*/
117140
public class McpModule implements Extension {
118141

modules/jooby-mcp/src/main/java/io/jooby/mcp/McpResult.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,55 @@
1717
import io.modelcontextprotocol.spec.McpError;
1818
import io.modelcontextprotocol.spec.McpSchema;
1919

20+
/**
21+
* Result mapping utility for the Model Context Protocol (MCP) integration.
22+
*
23+
* <p>This class acts as the bridge between standard Java/Kotlin return types and the strict
24+
* JSON-RPC payload structures required by the MCP specification. It is utilized heavily by the
25+
* APT-generated routing classes ({@code *Mcp_}) to seamlessly translate user-defined method outputs
26+
* into valid protocol responses.
27+
*
28+
* <h2>Conversion Strategies</h2>
29+
*
30+
* <ul>
31+
* <li><b>Pass-through:</b> If a method returns a native MCP schema object (e.g., {@link
32+
* McpSchema.CallToolResult}, {@link McpSchema.GetPromptResult}), it is returned as-is.
33+
* <li><b>Primitives & Strings:</b> Standard strings and primitives are automatically wrapped in
34+
* the appropriate textual content blocks (e.g., {@link McpSchema.TextContent}).
35+
* <li><b>POJOs & Collections:</b> Complex objects and lists are automatically serialized into
36+
* JSON strings using the configured {@link McpJsonMapper}, or passed as structured content
37+
* depending on the tool's schema capabilities.
38+
* <li><b>Prompts:</b> Raw strings or lists returned by a Prompt handler are automatically wrapped
39+
* in a {@link McpSchema.PromptMessage} assigned to the {@link McpSchema.Role#USER}.
40+
* </ul>
41+
*
42+
* <p>By handling these conversions internally, developers can write natural, idiomatic Java/Kotlin
43+
* methods without needing to couple their business logic to the MCP SDK classes.
44+
*
45+
* @author edgar
46+
* @since 4.2.0
47+
*/
2048
public class McpResult {
2149

2250
private final McpJsonMapper json;
2351

52+
/**
53+
* Creates a new result mapper using the provided JSON mapper for object serialization.
54+
*
55+
* @param json The JSON mapper instance.
56+
*/
2457
public McpResult(McpJsonMapper json) {
2558
this.json = json;
2659
}
2760

61+
/**
62+
* Converts a raw method return value into an MCP tool result.
63+
*
64+
* @param result The raw return value from the tool execution.
65+
* @param structuredContent True if complex objects should be returned as structured data objects,
66+
* false to serialize them as JSON text.
67+
* @return A valid {@link McpSchema.CallToolResult}.
68+
*/
2869
public McpSchema.CallToolResult toCallToolResult(Object result, boolean structuredContent) {
2970
try {
3071
if (result == null) {
@@ -50,6 +91,12 @@ public McpSchema.CallToolResult toCallToolResult(Object result, boolean structur
5091
}
5192
}
5293

94+
/**
95+
* Converts a raw method return value into an MCP prompt result.
96+
*
97+
* @param result The raw return value from the prompt execution.
98+
* @return A valid {@link McpSchema.GetPromptResult}.
99+
*/
53100
public McpSchema.GetPromptResult toPromptResult(Object result) {
54101
if (result == null) {
55102
return new McpSchema.GetPromptResult(null, List.of());
@@ -73,6 +120,13 @@ public McpSchema.GetPromptResult toPromptResult(Object result) {
73120
}
74121
}
75122

123+
/**
124+
* Converts a raw method return value into an MCP resource result.
125+
*
126+
* @param uri The requested resource URI.
127+
* @param result The raw return value from the resource execution.
128+
* @return A valid {@link McpSchema.ReadResourceResult}.
129+
*/
76130
public McpSchema.ReadResourceResult toResourceResult(String uri, Object result) {
77131
try {
78132
if (result == null) {
@@ -91,6 +145,12 @@ public McpSchema.ReadResourceResult toResourceResult(String uri, Object result)
91145
}
92146
}
93147

148+
/**
149+
* Converts a raw method return value into an MCP completion result.
150+
*
151+
* @param result The raw return value from the completion execution.
152+
* @return A valid {@link McpSchema.CompleteResult}.
153+
*/
94154
public McpSchema.CompleteResult toCompleteResult(Object result) {
95155
try {
96156
Objects.requireNonNull(result, "Completion result cannot be null");

0 commit comments

Comments
 (0)