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 aeba30dfd27be32c1ef141426fce3097cec56ab0
Author: James Bognar <[email protected]>
AuthorDate: Tue Jul 28 16:39:22 2026 -0400

    feat: add revision-neutral MCP value types (schema, role, content blocks)
    
    Co-authored-by: Cursor <[email protected]>
---
 .../juneau/rest/server/mcp/McpContentBlock.java    | 128 +++++++++++++++++++++
 .../rest/server/mcp/McpResourceContents.java       | 118 +++++++++++++++++++
 .../org/apache/juneau/rest/server/mcp/McpRole.java |  58 ++++++++++
 .../apache/juneau/rest/server/mcp/McpSchema.java   |  59 ++++++++++
 .../rest/server/mcp/McpNeutralValues_Test.java     |  98 ++++++++++++++++
 5 files changed, 461 insertions(+)

diff --git 
a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpContentBlock.java
 
b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpContentBlock.java
new file mode 100644
index 0000000000..f18c97b9bb
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpContentBlock.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server.mcp;
+
+/**
+ * Revision-neutral content block returned by tool calls and prompt messages.
+ *
+ * <p>
+ * Carries all three variants supported today — text, image, and embedded 
resource — so a handler
+ * cannot express less through the neutral model than it can through a wire 
bean. Supersedes the
+ * wire-level {@code Content} dictionary and its three leaf types.
+ */
+public final class McpContentBlock {
+
+       /** Which content variant is populated. */
+       public enum Kind {
+               /** {@link McpContentBlock#text()} is populated. */
+               TEXT,
+               /** {@link McpContentBlock#data()} and {@link 
McpContentBlock#mimeType()} are populated. */
+               IMAGE,
+               /** {@link McpContentBlock#resource()} is populated. */
+               RESOURCE
+       }
+
+       private final Kind kind;
+       private final String text;
+       private final String data;
+       private final String mimeType;
+       private final McpResourceContents resource;
+
+       private McpContentBlock(Kind kind, String text, String data, String 
mimeType, McpResourceContents resource) {
+               this.kind = kind;
+               this.text = text;
+               this.data = data;
+               this.mimeType = mimeType;
+               this.resource = resource;
+       }
+
+       /**
+        * Creates a text content block.
+        *
+        * @param text The text. Can be <jk>null</jk>.
+        * @return A new block. Never <jk>null</jk>.
+        */
+       public static McpContentBlock text(String text) {
+               return new McpContentBlock(Kind.TEXT, text, null, null, null);
+       }
+
+       /**
+        * Creates an image content block.
+        *
+        * @param data Base64-encoded image bytes. Can be <jk>null</jk>.
+        * @param mimeType The image media type. Can be <jk>null</jk>.
+        * @return A new block. Never <jk>null</jk>.
+        */
+       public static McpContentBlock image(String data, String mimeType) {
+               return new McpContentBlock(Kind.IMAGE, null, data, mimeType, 
null);
+       }
+
+       /**
+        * Creates an embedded-resource content block.
+        *
+        * @param resource The embedded payload. Can be <jk>null</jk>.
+        * @return A new block. Never <jk>null</jk>.
+        */
+       public static McpContentBlock resource(McpResourceContents resource) {
+               return new McpContentBlock(Kind.RESOURCE, null, null, null, 
resource);
+       }
+
+       /**
+        * Which variant this block carries.
+        *
+        * @return The variant. Never <jk>null</jk>.
+        */
+       public Kind kind() {
+               return kind;
+       }
+
+       /**
+        * The text payload.
+        *
+        * @return The text, or <jk>null</jk> unless this is a {@link 
Kind#TEXT} block.
+        */
+       public String text() {
+               return text;
+       }
+
+       /**
+        * The base64 image payload.
+        *
+        * @return The data, or <jk>null</jk> unless this is a {@link 
Kind#IMAGE} block.
+        */
+       public String data() {
+               return data;
+       }
+
+       /**
+        * The image media type.
+        *
+        * @return The media type, or <jk>null</jk> unless this is a {@link 
Kind#IMAGE} block.
+        */
+       public String mimeType() {
+               return mimeType;
+       }
+
+       /**
+        * The embedded resource payload.
+        *
+        * @return The payload, or <jk>null</jk> unless this is a {@link 
Kind#RESOURCE} block.
+        */
+       public McpResourceContents resource() {
+               return resource;
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpResourceContents.java
 
b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpResourceContents.java
new file mode 100644
index 0000000000..550231ff00
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpResourceContents.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server.mcp;
+
+/**
+ * Revision-neutral resource payload, either inline text or a base64 blob.
+ *
+ * <p>
+ * Supersedes the wire-level {@code ResourceContents} dictionary and its two 
leaf types. Used both
+ * by {@link McpContentBlock#resource(McpResourceContents)} and by {@link 
McpResourceOutcome}.
+ */
+public final class McpResourceContents {
+
+       /** Which payload variant is populated. */
+       public enum Kind {
+               /** {@link McpResourceContents#text()} is populated. */
+               TEXT,
+               /** {@link McpResourceContents#blob()} is populated. */
+               BLOB
+       }
+
+       private final Kind kind;
+       private final String uri;
+       private final String mimeType;
+       private final String text;
+       private final String blob;
+
+       private McpResourceContents(Kind kind, String uri, String mimeType, 
String text, String blob) {
+               this.kind = kind;
+               this.uri = uri;
+               this.mimeType = mimeType;
+               this.text = text;
+               this.blob = blob;
+       }
+
+       /**
+        * Creates an inline-text resource payload.
+        *
+        * @param uri The resource URI. Can be <jk>null</jk>.
+        * @param mimeType The media type. Can be <jk>null</jk>.
+        * @param text The text payload. Can be <jk>null</jk>.
+        * @return A new payload. Never <jk>null</jk>.
+        */
+       public static McpResourceContents text(String uri, String mimeType, 
String text) {
+               return new McpResourceContents(Kind.TEXT, uri, mimeType, text, 
null);
+       }
+
+       /**
+        * Creates a base64-blob resource payload.
+        *
+        * @param uri The resource URI. Can be <jk>null</jk>.
+        * @param mimeType The media type. Can be <jk>null</jk>.
+        * @param blob The base64-encoded payload. Can be <jk>null</jk>.
+        * @return A new payload. Never <jk>null</jk>.
+        */
+       public static McpResourceContents blob(String uri, String mimeType, 
String blob) {
+               return new McpResourceContents(Kind.BLOB, uri, mimeType, null, 
blob);
+       }
+
+       /**
+        * Which variant this payload carries.
+        *
+        * @return The variant. Never <jk>null</jk>.
+        */
+       public Kind kind() {
+               return kind;
+       }
+
+       /**
+        * The resource URI.
+        *
+        * @return The URI, or <jk>null</jk> if not set.
+        */
+       public String uri() {
+               return uri;
+       }
+
+       /**
+        * The media type.
+        *
+        * @return The media type, or <jk>null</jk> if not set.
+        */
+       public String mimeType() {
+               return mimeType;
+       }
+
+       /**
+        * The inline text payload.
+        *
+        * @return The text, or <jk>null</jk> for a {@link Kind#BLOB} payload.
+        */
+       public String text() {
+               return text;
+       }
+
+       /**
+        * The base64-encoded payload.
+        *
+        * @return The blob, or <jk>null</jk> for a {@link Kind#TEXT} payload.
+        */
+       public String blob() {
+               return blob;
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpRole.java
 
b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpRole.java
new file mode 100644
index 0000000000..751bed77c9
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpRole.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server.mcp;
+
+/**
+ * Revision-neutral role for {@link McpPromptMessage} entries.
+ *
+ * <p>
+ * MCP wire formats use lowercase strings; {@link #toWire()} and {@link 
#toString()} both return them.
+ */
+public enum McpRole {
+
+       /** User role. */
+       USER("user"),
+
+       /** Assistant role. */
+       ASSISTANT("assistant"),
+
+       /** System role. */
+       SYSTEM("system"),
+
+       /** Tool role. */
+       TOOL("tool");
+
+       private final String wire;
+
+       McpRole(String wire) {
+               this.wire = wire;
+       }
+
+       /**
+        * Wire token for JSON payloads.
+        *
+        * @return Lowercase MCP role string.
+        */
+       public String toWire() {
+               return wire;
+       }
+
+       @Override /* Object */
+       public String toString() {
+               return wire;
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpSchema.java
 
b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpSchema.java
new file mode 100644
index 0000000000..537e9ddc09
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpSchema.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server.mcp;
+
+import org.apache.juneau.marshall.collections.*;
+
+/**
+ * Revision-neutral carrier for a tool's input schema.
+ *
+ * <p>
+ * Deliberately a carrier, not a structured bean: the core has no opinion 
about which JSON Schema
+ * dialect or keyword set a revision supports. Each revision's adapter is 
responsible for validating
+ * that the carried map is expressible in its own wire schema type, and for 
rejecting configurations
+ * that are not.
+ *
+ * <p>
+ * The supplied map is <em>not</em> copied; callers must not mutate a map 
after handing it over.
+ */
+public final class McpSchema {
+
+       private final JsonMap raw;
+
+       private McpSchema(JsonMap raw) {
+               this.raw = raw;
+       }
+
+       /**
+        * Creates a schema carrier around a raw JSON Schema object.
+        *
+        * @param raw The schema as a JSON object. Can be <jk>null</jk>, which 
yields an empty schema.
+        * @return A new carrier. Never <jk>null</jk>.
+        */
+       public static McpSchema of(JsonMap raw) {
+               return new McpSchema(raw == null ? new JsonMap() : raw);
+       }
+
+       /**
+        * The carried schema.
+        *
+        * @return The schema map. Never <jk>null</jk>; not a copy.
+        */
+       public JsonMap toJsonMap() {
+               return raw;
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpNeutralValues_Test.java
 
b/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpNeutralValues_Test.java
new file mode 100644
index 0000000000..c5f6e12fe7
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpNeutralValues_Test.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server.mcp;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.marshall.collections.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Coverage for the revision-neutral value types: {@link McpSchema}, {@link 
McpRole},
+ * {@link McpContentBlock}, {@link McpResourceContents}.
+ */
+class McpNeutralValues_Test {
+
+       @Test
+       void a01_schema_carriesRawMapThrough() {
+               var a = JsonMap.of("type", "object");
+               var b = McpSchema.of(a);
+               assertSame(a, b.toJsonMap());
+       }
+
+       @Test
+       void a02_schema_nullBecomesEmptyMap() {
+               assertTrue(McpSchema.of(null).toJsonMap().isEmpty());
+       }
+
+       @Test
+       void b01_role_hasFourValuesWithLowercaseWire() {
+               assertEquals(4, McpRole.values().length);
+               assertEquals("user", McpRole.USER.toWire());
+               assertEquals("assistant", McpRole.ASSISTANT.toWire());
+               assertEquals("system", McpRole.SYSTEM.toWire());
+               assertEquals("tool", McpRole.TOOL.toWire());
+               for (var a : McpRole.values())
+                       assertEquals(a.toWire(), a.toString());
+       }
+
+       @Test
+       void c01_resourceContents_text() {
+               var a = McpResourceContents.text("file:///a", "text/plain", 
"body");
+               assertEquals(McpResourceContents.Kind.TEXT, a.kind());
+               assertEquals("file:///a", a.uri());
+               assertEquals("text/plain", a.mimeType());
+               assertEquals("body", a.text());
+               assertNull(a.blob());
+       }
+
+       @Test
+       void c02_resourceContents_blob() {
+               var a = McpResourceContents.blob("file:///b", 
"application/octet-stream", "Qk09");
+               assertEquals(McpResourceContents.Kind.BLOB, a.kind());
+               assertEquals("Qk09", a.blob());
+               assertNull(a.text());
+       }
+
+       @Test
+       void d01_contentBlock_text() {
+               var a = McpContentBlock.text("hi");
+               assertEquals(McpContentBlock.Kind.TEXT, a.kind());
+               assertEquals("hi", a.text());
+               assertNull(a.data());
+               assertNull(a.mimeType());
+               assertNull(a.resource());
+       }
+
+       @Test
+       void d02_contentBlock_image() {
+               var a = McpContentBlock.image("AAA=", "image/png");
+               assertEquals(McpContentBlock.Kind.IMAGE, a.kind());
+               assertEquals("AAA=", a.data());
+               assertEquals("image/png", a.mimeType());
+               assertNull(a.text());
+       }
+
+       @Test
+       void d03_contentBlock_resource() {
+               var a = McpResourceContents.text("file:///a", null, "x");
+               var b = McpContentBlock.resource(a);
+               assertEquals(McpContentBlock.Kind.RESOURCE, b.kind());
+               assertSame(a, b.resource());
+               assertNull(b.text());
+       }
+}

Reply via email to