This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git

commit 0ac1ac4fcd997f1de4c3e5566689647aef879823
Author: James Bognar <[email protected]>
AuthorDate: Tue Jul 28 19:09:45 2026 -0400

    test: cover MCP 2025-06-18 wire content/role branches, mark HTT for 
unreachable throws
    
    Task 16 coverage sweep found gaps in Mcp20250618Wire's toNeutral/toWire
    conversion for image/embedded-resource content and non-USER prompt-message
    roles, plus McpPromptArgument/McpPromptSpec argument round-tripping. Added
    two focused tests to McpTypedHandlers_Test covering those paths.
    
    The two "unsupported type" throw branches in Mcp20250618Wire are
    unreachable through the public API (guarded by prior validation), so they
    are marked // HTT rather than contorted with reflection-based tests.
    McpRestServlet's compareAndSet race-condition branch is similarly HTT.
    
    Co-authored-by: Cursor <[email protected]>
---
 .../rest/server/mcp/v20250618/Mcp20250618Wire.java |  4 +-
 .../mcp/v20250618/McpTypedHandlers_Test.java       | 64 ++++++++++++++++++++++
 .../juneau/rest/server/mcp/McpRestServlet.java     |  2 +-
 3 files changed, 67 insertions(+), 3 deletions(-)

diff --git 
a/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/Mcp20250618Wire.java
 
b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/Mcp20250618Wire.java
index 9b7b4aac4e..d4e848f828 100644
--- 
a/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/Mcp20250618Wire.java
+++ 
b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/Mcp20250618Wire.java
@@ -188,7 +188,7 @@ final class Mcp20250618Wire {
                        return McpContentBlock.image(x2.getData(), 
x2.getMimeType());
                if (x instanceof EmbeddedResourceContent x2)
                        return 
McpContentBlock.resource(toNeutral(x2.getResource()));
-               throw new McpException(Mcp20250618Revision.CODE_INTERNAL_ERROR, 
"Unsupported content type: " + x.getClass().getName());
+               throw new McpException(Mcp20250618Revision.CODE_INTERNAL_ERROR, 
"Unsupported content type: " + x.getClass().getName()); // HTT: the Content 
dictionary is closed to Text/Image/EmbeddedResource; a 4th implementation 
cannot occur through the public API.
        }
 
        static McpResourceContents toNeutral(ResourceContents x) {
@@ -198,7 +198,7 @@ final class Mcp20250618Wire {
                        return McpResourceContents.text(x2.getUri(), 
x2.getMimeType(), x2.getText());
                if (x instanceof BlobResourceContents x2)
                        return McpResourceContents.blob(x2.getUri(), 
x2.getMimeType(), x2.getBlob());
-               throw new McpException(Mcp20250618Revision.CODE_INTERNAL_ERROR, 
"Unsupported resource contents type: " + x.getClass().getName());
+               throw new McpException(Mcp20250618Revision.CODE_INTERNAL_ERROR, 
"Unsupported resource contents type: " + x.getClass().getName()); // HTT: the 
ResourceContents dictionary is closed to Text/Blob; a 3rd implementation cannot 
occur through the public API.
        }
 
        static McpPromptSpec toNeutral(Prompt x) {
diff --git 
a/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedHandlers_Test.java
 
b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedHandlers_Test.java
index a01ba476c6..433746f88e 100644
--- 
a/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedHandlers_Test.java
+++ 
b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedHandlers_Test.java
@@ -273,6 +273,70 @@ class McpTypedHandlers_Test {
                assertEquals(Mcp20250618Revision.CODE_INTERNAL_ERROR, 
resp.getError().getCode());
        }
 
+       @Test
+       void 
a08_typedTool_callToolResultWithImageAndResourceContent_roundTrips() {
+               var ctr = new CallToolResult().setContent(List.of(
+                       new 
ImageContent().setData("aW1n").setMimeType("image/png"),
+                       new EmbeddedResourceContent().setResource(new 
TextResourceContents().setUri("r://x").setMimeType("text/plain").setText("inline")),
+                       new EmbeddedResourceContent().setResource(new 
BlobResourceContents().setUri("r://y").setMimeType("application/octet-stream").setBlob("QUJD"))));
+               var typed = new McpTypedToolHandler<EchoArgs,CallToolResult>() {
+                       @Override
+                       public Tool descriptor() { return new 
Tool().setName("m"); }
+                       @Override
+                       public Class<EchoArgs> argumentType() { return 
EchoArgs.class; }
+                       @Override
+                       public CallToolResult call(EchoArgs args, BeanStore 
ctx) { return ctr; }
+               };
+               var config = new 
McpServerConfig().addTool(McpTypedHandlers.adaptTool(typed));
+               var resp = dispatch(new JsonRpcRequest()
+                       .setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
+                       
.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "m")), config);
+               var result = (CallToolResult) resp.getResult();
+               assertSize(3, result.getContent());
+               assertString("aW1n", ((ImageContent) 
result.getContent().get(0)).getData());
+               var textResource = (EmbeddedResourceContent) 
result.getContent().get(1);
+               assertString("inline", ((TextResourceContents) 
textResource.getResource()).getText());
+               var blobResource = (EmbeddedResourceContent) 
result.getContent().get(2);
+               assertString("QUJD", ((BlobResourceContents) 
blobResource.getResource()).getBlob());
+       }
+
+       @Test
+       void b03_typedPrompt_descriptorArgumentsAndAllRoleMessages_roundTrip() {
+               var typed = new McpTypedPromptHandler<EchoArgs>() {
+                       @Override
+                       public Prompt descriptor() {
+                               return new 
Prompt().setName("m").setArguments(List.of(new 
PromptArgument().setName("who").setRequired(true)));
+                       }
+                       @Override
+                       public Class<EchoArgs> argumentType() { return 
EchoArgs.class; }
+                       @Override
+                       public GetPromptResult get(EchoArgs args, BeanStore 
ctx) {
+                               return new 
GetPromptResult().setMessages(List.of(
+                                       new 
PromptMessage().setRole(Role.USER).setContent(new TextContent().setText("hi")),
+                                       new 
PromptMessage().setRole(Role.ASSISTANT).setContent(new 
TextContent().setText("hello")),
+                                       new 
PromptMessage().setRole(Role.SYSTEM).setContent(new 
TextContent().setText("sys")),
+                                       new 
PromptMessage().setRole(Role.TOOL).setContent(new 
TextContent().setText("tool"))));
+                       }
+               };
+               var raw = McpTypedHandlers.adaptPrompt(typed);
+               var config = new McpServerConfig().addPrompt(raw);
+
+               var list = (ListPromptsResult) dispatch(new JsonRpcRequest()
+                       .setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
+                       .setMethod(McpMethods.PROMPTS_LIST).setParams(null), 
config).getResult();
+               assertSize(1, list.getPrompts().get(0).getArguments());
+               assertString("who", 
list.getPrompts().get(0).getArguments().get(0).getName());
+
+               var pr = (GetPromptResult) dispatch(new JsonRpcRequest()
+                       .setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
+                       
.setMethod(McpMethods.PROMPTS_GET).setParams(JsonMap.of("name", "m")), 
config).getResult();
+               assertSize(4, pr.getMessages());
+               assertEquals(Role.USER, pr.getMessages().get(0).getRole());
+               assertEquals(Role.ASSISTANT, pr.getMessages().get(1).getRole());
+               assertEquals(Role.SYSTEM, pr.getMessages().get(2).getRole());
+               assertEquals(Role.TOOL, pr.getMessages().get(3).getRole());
+       }
+
        @Test
        void b02_typedPrompt_argsBoundAndResult() {
                var typed = new McpTypedPromptHandler<EchoArgs>() {
diff --git 
a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpRestServlet.java
 
b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpRestServlet.java
index b51302eee3..8ed3097820 100644
--- 
a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpRestServlet.java
+++ 
b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpRestServlet.java
@@ -83,7 +83,7 @@ public abstract class McpRestServlet extends BasicRestServlet 
{
                        var nc = createMcpConfig();
                        if (nc == null)
                                throw new 
IllegalStateException("createMcpConfig() returned null");
-                       c = config.compareAndSet(null, nc) ? nc : config.get();
+                       c = config.compareAndSet(null, nc) ? nc : config.get(); 
// HTT: the CAS-loses branch requires a genuine concurrent first-access race; 
untestable deterministically.
                }
                return c;
        }

Reply via email to