Skip to content

Commit d33b90b

Browse files
committed
- bug fixing session/code refactor
- move transport as internal package - add mcp-inspector - add automatic-module-name - fix error for missing completions
1 parent a4a115b commit d33b90b

File tree

21 files changed

+580
-391
lines changed

21 files changed

+580
-391
lines changed

modules/jooby-apt/src/main/java/io/jooby/internal/apt/McpRoute.java

Lines changed: 109 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -838,7 +838,8 @@ public List<String> generateMcpHandlerMethod(boolean kt) {
838838
if (kt) {
839839
buffer.add(
840840
statement(indent(6), "val raw_", javaName, " = args.get(", string(mcpName), ")"));
841-
if (!isNullable)
841+
842+
if (!isNullable) {
842843
buffer.add(
843844
statement(
844845
indent(6),
@@ -847,16 +848,73 @@ public List<String> generateMcpHandlerMethod(boolean kt) {
847848
" == null) throw IllegalArgumentException(",
848849
string("Missing req param: " + mcpName),
849850
")"));
850-
buffer.add(
851-
statement(
852-
indent(6),
853-
"val ",
854-
javaName,
855-
" = raw_",
856-
javaName,
857-
" as ",
858-
type,
859-
isNullable ? "?" : ""));
851+
}
852+
853+
boolean isNumber = isNumber(type);
854+
855+
if (isNumber) {
856+
String ktType = "Int";
857+
if (type.contains("Double") || type.equals("double")) ktType = "Double";
858+
else if (type.contains("Long") || type.equals("long")) ktType = "Long";
859+
else if (type.contains("Float") || type.equals("float")) ktType = "Float";
860+
else if (type.contains("Short") || type.equals("short")) ktType = "Short";
861+
else if (type.contains("Byte") || type.equals("byte")) ktType = "Byte";
862+
863+
if (isNullable) {
864+
buffer.add(
865+
statement(
866+
indent(6),
867+
"val ",
868+
javaName,
869+
" = (raw_",
870+
javaName,
871+
" as? Number)?.to",
872+
ktType,
873+
"()"));
874+
} else {
875+
buffer.add(
876+
statement(
877+
indent(6),
878+
"val ",
879+
javaName,
880+
" = (raw_",
881+
javaName,
882+
" as Number).to",
883+
ktType,
884+
"()"));
885+
}
886+
} else if (type.equals("java.lang.String") || type.equals("String")) {
887+
buffer.add(
888+
statement(
889+
indent(6),
890+
"val ",
891+
javaName,
892+
" = raw_",
893+
javaName,
894+
isNullable ? "?.toString()" : ".toString()"));
895+
} else if (type.equals("boolean") || type.equals("java.lang.Boolean")) {
896+
buffer.add(
897+
statement(
898+
indent(6),
899+
"val ",
900+
javaName,
901+
" = raw_",
902+
javaName,
903+
" as Boolean",
904+
isNullable ? "?" : ""));
905+
} else {
906+
buffer.add(
907+
statement(
908+
indent(6),
909+
"val ",
910+
javaName,
911+
" = raw_",
912+
javaName,
913+
" as ",
914+
type,
915+
isNullable ? "?" : ""));
916+
}
917+
860918
} else {
861919
buffer.add(
862920
statement(
@@ -867,7 +925,8 @@ public List<String> generateMcpHandlerMethod(boolean kt) {
867925
string(mcpName),
868926
")",
869927
semicolon(kt)));
870-
if (!isNullable)
928+
929+
if (!isNullable) {
871930
buffer.add(
872931
statement(
873932
indent(6),
@@ -877,24 +936,36 @@ public List<String> generateMcpHandlerMethod(boolean kt) {
877936
string("Missing req param: " + mcpName),
878937
")",
879938
semicolon(kt)));
939+
}
940+
941+
boolean isNumber = isNumber(type);
942+
943+
if (isNumber) {
944+
String primitiveName =
945+
switch (type) {
946+
case "double", "java.lang.Double" -> "double";
947+
case "long", "java.lang.Long" -> "long";
948+
case "float", "java.lang.Float" -> "float";
949+
case "short", "java.lang.Short" -> "short";
950+
case "byte", "java.lang.Byte" -> "byte";
951+
default -> "int";
952+
};
880953

881-
if (type.equals("int") || type.equals("java.lang.Integer")) {
882954
buffer.add(
883955
statement(
884956
indent(6),
885957
"var ",
886958
javaName,
887959
" = ",
888960
isNullable ? "(raw_" + javaName + " == null) ? null : " : "",
889-
"raw_",
890-
javaName,
891-
" instanceof Number ? ((Number) raw_",
961+
"((Number) raw_",
892962
javaName,
893-
").intValue() : Integer.parseInt(raw_",
894-
javaName,
895-
".toString())",
963+
").",
964+
primitiveName,
965+
"Value()",
896966
semicolon(kt)));
897-
} else if (type.equals("java.lang.String")) {
967+
968+
} else if (type.equals("java.lang.String") || type.equals("String")) {
898969
buffer.add(
899970
statement(
900971
indent(6),
@@ -906,6 +977,9 @@ public List<String> generateMcpHandlerMethod(boolean kt) {
906977
javaName,
907978
".toString() : null",
908979
semicolon(kt)));
980+
} else if (type.equals("boolean") || type.equals("java.lang.Boolean")) {
981+
buffer.add(
982+
statement(indent(6), "var ", javaName, " = (Boolean) raw_", javaName, semicolon(kt)));
909983
} else {
910984
buffer.add(
911985
statement(
@@ -981,6 +1055,21 @@ public List<String> generateMcpHandlerMethod(boolean kt) {
9811055
return buffer;
9821056
}
9831057

1058+
private static boolean isNumber(String type) {
1059+
return type.equals("int")
1060+
|| type.equals("java.lang.Integer")
1061+
|| type.equals("double")
1062+
|| type.equals("java.lang.Double")
1063+
|| type.equals("long")
1064+
|| type.equals("java.lang.Long")
1065+
|| type.equals("float")
1066+
|| type.equals("java.lang.Float")
1067+
|| type.equals("short")
1068+
|| type.equals("java.lang.Short")
1069+
|| type.equals("byte")
1070+
|| type.equals("java.lang.Byte");
1071+
}
1072+
9841073
private boolean hasOutputSchema() {
9851074
var returnTypeStr = getReturnType().getRawType().toString();
9861075
var isPrimitive =

modules/jooby-apt/src/main/java/io/jooby/internal/apt/McpRouter.java

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,37 @@ public String toSourceCode(boolean kt) throws IOException {
152152
completionGroups.computeIfAbsent(ref, k -> new ArrayList<>()).add(route);
153153
}
154154

155+
// Gather all valid references so we can register dummy completions for targets that lack them
156+
var allCompletionRefs = new java.util.LinkedHashSet<String>();
157+
for (var route : prompts) {
158+
var annotation =
159+
AnnotationSupport.findAnnotationByName(
160+
route.getMethod(), "io.jooby.annotation.mcp.McpPrompt");
161+
var name =
162+
annotation != null
163+
? AnnotationSupport.findAnnotationValue(annotation, "name"::equals).stream()
164+
.findFirst()
165+
.orElse("")
166+
: "";
167+
if (name.isEmpty()) name = route.getMethodName();
168+
allCompletionRefs.add(name);
169+
}
170+
for (var route : resources) {
171+
if (route.isMcpResourceTemplate()) {
172+
var annotation =
173+
AnnotationSupport.findAnnotationByName(
174+
route.getMethod(), "io.jooby.annotation.mcp.McpResource");
175+
var uri =
176+
annotation != null
177+
? AnnotationSupport.findAnnotationValue(annotation, "uri"::equals).stream()
178+
.findFirst()
179+
.orElse("")
180+
: "";
181+
allCompletionRefs.add(uri);
182+
}
183+
}
184+
allCompletionRefs.addAll(completionGroups.keySet());
185+
155186
if (kt) {
156187
buffer.append(
157188
statement(
@@ -233,18 +264,29 @@ public String toSourceCode(boolean kt) throws IOException {
233264
semicolon(kt)));
234265
}
235266

236-
for (var ref : completionGroups.keySet()) {
267+
// Loop over ALL possible refs, not just the ones with explicit handlers
268+
for (var ref : allCompletionRefs) {
237269
var isResource = ref.contains("://");
238-
var handlerName = findTargetMethodName(ref) + "CompletionHandler";
239270
var refObj =
240271
isResource
241272
? "io.modelcontextprotocol.spec.McpSchema.ResourceReference"
242273
: "io.modelcontextprotocol.spec.McpSchema.PromptReference";
243274

244-
String lambda =
245-
kt
246-
? "{ exchange, req -> this." + handlerName + "(exchange, null, req) }"
247-
: "(exchange, req) -> this." + handlerName + "(exchange, null, req)";
275+
String lambda;
276+
if (completionGroups.containsKey(ref)) {
277+
var handlerName = findTargetMethodName(ref) + "CompletionHandler";
278+
lambda =
279+
kt
280+
? "{ exchange, req -> this." + handlerName + "(exchange, null, req) }"
281+
: "(exchange, req) -> this." + handlerName + "(exchange, null, req)";
282+
} else {
283+
// Fallback: Return an empty completion result safely
284+
lambda =
285+
kt
286+
? "{ _, _ -> io.jooby.mcp.McpResult(this.json).toCompleteResult(emptyList<Any>()) }"
287+
: "(exchange, req) -> new"
288+
+ " io.jooby.mcp.McpResult(this.json).toCompleteResult(java.util.List.of())";
289+
}
248290

249291
if (kt) {
250292
buffer.append(
@@ -305,18 +347,29 @@ public String toSourceCode(boolean kt) throws IOException {
305347
semicolon(kt)));
306348
}
307349

308-
for (var ref : completionGroups.keySet()) {
350+
// Loop over ALL possible refs
351+
for (var ref : allCompletionRefs) {
309352
var isResource = ref.contains("://");
310-
var handlerName = findTargetMethodName(ref) + "CompletionHandler";
311353
var refObj =
312354
isResource
313355
? "io.modelcontextprotocol.spec.McpSchema.ResourceReference"
314356
: "io.modelcontextprotocol.spec.McpSchema.PromptReference";
315357

316-
String lambda =
317-
kt
318-
? "{ ctx, req -> this." + handlerName + "(null, ctx, req) }"
319-
: "(ctx, req) -> this." + handlerName + "(null, ctx, req)";
358+
String lambda;
359+
if (completionGroups.containsKey(ref)) {
360+
var handlerName = findTargetMethodName(ref) + "CompletionHandler";
361+
lambda =
362+
kt
363+
? "{ ctx, req -> this." + handlerName + "(null, ctx, req) }"
364+
: "(ctx, req) -> this." + handlerName + "(null, ctx, req)";
365+
} else {
366+
// Fallback: Return an empty completion result safely
367+
lambda =
368+
kt
369+
? "{ _, _ -> io.jooby.mcp.McpResult(this.json).toCompleteResult(emptyList<Any>()) }"
370+
: "(ctx, req) -> new"
371+
+ " io.jooby.mcp.McpResult(this.json).toCompleteResult(java.util.List.of())";
372+
}
320373

321374
if (kt) {
322375
buffer.append(

0 commit comments

Comments
 (0)