You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2026/07/29 17:11:28 UTC

(juneau) 10/19: refactor: replace McpDispatcher/Mcp with the McpRevision SPI

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 3420e01ccb7894080c9d26e92d48029e2ad2bc72
Author: James Bognar <ja...@apache.org>
AuthorDate: Tue Jul 28 17:21:46 2026 -0400

    refactor: replace McpDispatcher/Mcp with the McpRevision SPI
    
    Moves the JSON-RPC method table verbatim into Mcp20250618Revision in the new
    2025-06-18 adapter, rewires McpRestServlet/McpEndpoint to dispatch through
    revision() with an McpExchange, and deletes McpDispatcher and the Mcp static
    facade. Handler interfaces and McpServerConfig still speak wire types; the
    neutral model lands next. Characterization fixtures replay byte-identical.
    
    Co-authored-by: Cursor <cu...@cursor.com>
---
 .../juneau/rest/server/mcp/McpCursorCtx_Test.java  |  47 ++--
 .../Mcp20250618Revision_Test.java}                 |  59 +++--
 .../{ => v20250618}/McpHandlerDefaults_Test.java   |  28 +--
 .../McpRestServlet20250618_Test.java}              |  13 +-
 .../mcp/{ => v20250618}/McpServerConfig_Test.java  |   3 +-
 .../mcp/{ => v20250618}/McpTypedHandlers_Test.java |  45 ++--
 .../server/mcp/v20250618/Mcp20250618Revision.java  | 230 +++++++++++++++++
 .../server/mcp/v20250618/McpEndpoint20250618.java  |  66 +++++
 .../mcp/v20250618/McpRestServlet20250618.java      |  75 ++++++
 .../server/mcp/v20250618}/McpTypedHandlers.java    |   9 +-
 .../mcp/v20250618}/McpTypedPromptHandler.java      |   3 +-
 .../server/mcp/v20250618}/McpTypedToolHandler.java |   3 +-
 .../mcp/v20250618}/Characterization_Test.java      |  15 +-
 .../mcp/v20250618/Mcp20250618Revision_Test.java}   |  59 +++--
 .../mcp/v20250618}/McpHandlerDefaults_Test.java    |  28 +--
 .../v20250618/McpRestServlet20250618_Test.java}    |  13 +-
 .../mcp/v20250618}/McpServerConfig_Test.java       |   3 +-
 .../mcp/v20250618}/McpTypedHandlers_Test.java      |  45 ++--
 .../characterization/CAPS-initialize.request.json  |   0
 .../characterization/CAPS-initialize.response.json |   0
 .../EMPTY-empty-method.request.json                |   0
 .../EMPTY-empty-method.response.json               |   0
 .../characterization/EMPTY-initialize.request.json |   0
 .../EMPTY-initialize.response.json                 |   0
 .../EMPTY-missing-method.request.json              |   0
 .../EMPTY-missing-method.response.json             |   0
 .../EMPTY-notification-ping.request.json           |   0
 .../EMPTY-notification-ping.response.json          |   0
 .../EMPTY-notification-unknown.request.json        |   0
 .../EMPTY-notification-unknown.response.json       |   0
 .../characterization/EMPTY-ping.request.json       |   0
 .../characterization/EMPTY-ping.response.json      |   0
 .../EMPTY-prompts-get-missing-name.request.json    |   0
 .../EMPTY-prompts-get-missing-name.response.json   |   0
 .../EMPTY-prompts-get-unknown.request.json         |   0
 .../EMPTY-prompts-get-unknown.response.json        |   0
 .../EMPTY-prompts-list.request.json                |   0
 .../EMPTY-prompts-list.response.json               |   0
 .../EMPTY-resources-list.request.json              |   0
 .../EMPTY-resources-list.response.json             |   0
 .../EMPTY-resources-read-missing-uri.request.json  |   0
 .../EMPTY-resources-read-missing-uri.response.json |   0
 .../EMPTY-resources-read-unknown.request.json      |   0
 .../EMPTY-resources-read-unknown.response.json     |   0
 .../EMPTY-tools-call-missing-name.request.json     |   0
 .../EMPTY-tools-call-missing-name.response.json    |   0
 .../EMPTY-tools-call-unknown-tool.request.json     |   0
 .../EMPTY-tools-call-unknown-tool.response.json    |   0
 .../characterization/EMPTY-tools-list.request.json |   0
 .../EMPTY-tools-list.response.json                 |   0
 .../EMPTY-unknown-method.request.json              |   0
 .../EMPTY-unknown-method.response.json             |   0
 .../characterization/FULL-initialize.request.json  |   0
 .../characterization/FULL-initialize.response.json |   0
 .../characterization/FULL-prompts-get.request.json |   0
 .../FULL-prompts-get.response.json                 |   0
 .../FULL-prompts-list.request.json                 |   0
 .../FULL-prompts-list.response.json                |   0
 .../FULL-resources-list.request.json               |   0
 .../FULL-resources-list.response.json              |   0
 .../FULL-resources-read.request.json               |   0
 .../FULL-resources-read.response.json              |   0
 .../characterization/FULL-string-id.request.json   |   0
 .../characterization/FULL-string-id.response.json  |   0
 .../FULL-tools-call-arguments-not-map.request.json |   0
 ...FULL-tools-call-arguments-not-map.response.json |   0
 .../FULL-tools-call-error-flag.request.json        |   0
 .../FULL-tools-call-error-flag.response.json       |   0
 .../FULL-tools-call-mixed-content.request.json     |   0
 .../FULL-tools-call-mixed-content.response.json    |   0
 .../FULL-tools-call-no-arguments.request.json      |   0
 .../FULL-tools-call-no-arguments.response.json     |   0
 .../FULL-tools-call-params-not-map.request.json    |   0
 .../FULL-tools-call-params-not-map.response.json   |   0
 .../FULL-tools-call-text.request.json              |   0
 .../FULL-tools-call-text.response.json             |   0
 .../characterization/FULL-tools-list.request.json  |   0
 .../characterization/FULL-tools-list.response.json |   0
 .../PAGED-tools-list-first.request.json            |   0
 .../PAGED-tools-list-first.response.json           |   0
 .../PAGED-tools-list-second.request.json           |   0
 .../PAGED-tools-list-second.response.json          |   0
 .../THROW-mcp-exception.request.json               |   0
 .../THROW-mcp-exception.response.json              |   0
 .../characterization/THROW-no-message.request.json |   0
 .../THROW-no-message.response.json                 |   0
 .../THROW-runtime-exception.request.json           |   0
 .../THROW-runtime-exception.response.json          |   0
 .../org/apache/juneau/rest/server/mcp/Mcp.java     |  59 -----
 .../juneau/rest/server/mcp/McpDispatcher.java      | 271 ---------------------
 .../apache/juneau/rest/server/mcp/McpEndpoint.java |  23 +-
 .../juneau/rest/server/mcp/McpPromptHandler.java   |   5 +-
 .../juneau/rest/server/mcp/McpResourceHandler.java |   5 +-
 .../juneau/rest/server/mcp/McpRestServlet.java     |  21 +-
 .../juneau/rest/server/mcp/McpServerConfig.java    |   6 +-
 .../juneau/rest/server/mcp/McpToolHandler.java     |   5 +-
 .../juneau/rest/server/mcp/McpCursorCtx_Test.java} |  47 ++--
 97 files changed, 610 insertions(+), 576 deletions(-)

diff --git a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpTypedPromptHandler.java b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpCursorCtx_Test.java
similarity index 53%
copy from juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpTypedPromptHandler.java
copy to juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpCursorCtx_Test.java
index 524737d38a..4ee720fd73 100644
--- a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpTypedPromptHandler.java
+++ b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpCursorCtx_Test.java
@@ -16,36 +16,29 @@
  */
 package org.apache.juneau.rest.server.mcp;
 
-import org.apache.juneau.bean.mcp.v20250618.*;
+import static org.junit.jupiter.api.Assertions.*;
+
 import org.apache.juneau.commons.inject.*;
+import org.junit.jupiter.api.*;
 
 /**
- * Typed variant of {@link McpPromptHandler} where MCP {@code prompts/get} arguments bind into a Juneau bean.
- *
- * @param <A> Argument bean type.
+ * Coverage for {@link McpCursor} bean-store passthrough.
  */
-public interface McpTypedPromptHandler<A> {
-
-	/**
-	 * Returns the static descriptor for this prompt.
-	 *
-	 * @return The prompt descriptor. Never {@code null}.
-	 */
-	Prompt descriptor();
-
-	/**
-	 * Returns the runtime argument class for binding.
-	 *
-	 * @return The argument class. Never {@code null}.
-	 */
-	Class<A> argumentType();
+class McpCursorCtx_Test {
 
-	/**
-	 * Renders the prompt.
-	 *
-	 * @param arguments Bound argument bean (may be {@code null} when no arguments are supplied).
-	 * @param ctx Per-request bean store.
-	 * @return The rendered prompt.
-	 */
-	GetPromptResult get(A arguments, BeanStore ctx);
+	@Test
+	void cursor_passes_ctx_to_strategy() {
+		// Verify ctx parameter reaches the cursor (covers the BeanStore parameter passthrough).
+		var bs = new BasicBeanStore();
+		var got = new Object[1];
+		McpCursor c = new McpCursor() {
+			@Override
+			public <T> McpPage<T> page(java.util.List<T> all, String cursor, BeanStore ctx) {
+				got[0] = ctx;
+				return new McpPage<>(all, null);
+			}
+		};
+		c.page(java.util.List.of(), null, bs);
+		assertSame(bs, got[0]);
+	}
 }
diff --git a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpDispatcher_Test.java b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/Mcp20250618Revision_Test.java
similarity index 82%
rename from juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpDispatcher_Test.java
rename to juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/Mcp20250618Revision_Test.java
index f2773d7d6d..5deec891fa 100644
--- a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpDispatcher_Test.java
+++ b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/Mcp20250618Revision_Test.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.juneau.rest.server.mcp;
+package org.apache.juneau.rest.server.mcp.v20250618;
 
 import static org.apache.juneau.test.bct.BctAssertions.*;
 import static org.junit.jupiter.api.Assertions.*;
@@ -25,14 +25,14 @@ import org.apache.juneau.bean.jsonrpc.*;
 import org.apache.juneau.bean.mcp.v20250618.*;
 import org.apache.juneau.commons.inject.*;
 import org.apache.juneau.marshall.collections.*;
+import org.apache.juneau.rest.server.mcp.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Coverage for {@link McpDispatcher}.
+ * Coverage for {@link Mcp20250618Revision}.
  */
-class McpDispatcher_Test {
+class Mcp20250618Revision_Test {
 
-	private final McpDispatcher dispatcher = new McpDispatcher();
 	private final BeanStore ctx = new BasicBeanStore();
 
 	private static McpToolHandler tool(String name, java.util.function.Function<Map<String,Object>,CallToolResult> fn) {
@@ -82,7 +82,7 @@ class McpDispatcher_Test {
 	}
 
 	private JsonRpcResponse send(McpServerConfig config, JsonRpcRequest r) {
-		return dispatcher.dispatch(r, config, ctx);
+		return new Mcp20250618Revision(null).dispatch(new McpExchange(r, n -> null), config, ctx);
 	}
 
 	@Test
@@ -99,7 +99,7 @@ class McpDispatcher_Test {
 		assertNotNull(result.getCapabilities().getTools());
 		assertNotNull(result.getCapabilities().getPrompts());
 		assertNotNull(result.getCapabilities().getResources());
-		assertString(McpDispatcher.DEFAULT_SERVER_NAME, result.getServerInfo().getName());
+		assertString(Mcp20250618Revision.DEFAULT_SERVER_NAME, result.getServerInfo().getName());
 	}
 
 	@Test
@@ -123,19 +123,19 @@ class McpDispatcher_Test {
 	@Test
 	void method_notFound() {
 		var resp = send(new McpServerConfig(), req(1, "no/such/method", null));
-		assertEquals(McpDispatcher.CODE_METHOD_NOT_FOUND, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_METHOD_NOT_FOUND, resp.getError().getCode());
 	}
 
 	@Test
 	void missing_method() {
 		var resp = send(new McpServerConfig(), req(1, null, null));
-		assertEquals(McpDispatcher.CODE_INVALID_REQUEST, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INVALID_REQUEST, resp.getError().getCode());
 	}
 
 	@Test
 	void empty_method_string() {
 		var resp = send(new McpServerConfig(), req(1, "", null));
-		assertEquals(McpDispatcher.CODE_INVALID_REQUEST, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INVALID_REQUEST, resp.getError().getCode());
 	}
 
 	@Test
@@ -183,8 +183,8 @@ class McpDispatcher_Test {
 
 	@Test
 	void nullEnvelope_returnsInvalidRequest() {
-		var resp = dispatcher.dispatch(null, new McpServerConfig(), ctx);
-		assertEquals(McpDispatcher.CODE_INVALID_REQUEST, resp.getError().getCode());
+		var resp = new Mcp20250618Revision(null).dispatch(new McpExchange(null, n -> null), new McpServerConfig(), ctx);
+		assertEquals(Mcp20250618Revision.CODE_INVALID_REQUEST, resp.getError().getCode());
 	}
 
 	// -------- tools/list ---------
@@ -230,27 +230,27 @@ class McpDispatcher_Test {
 	@Test
 	void tools_call_missingName_invalidParams() {
 		var resp = send(new McpServerConfig(), req(1, McpMethods.TOOLS_CALL, JsonMap.of("arguments", JsonMap.of())));
-		assertEquals(McpDispatcher.CODE_INVALID_PARAMS, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INVALID_PARAMS, resp.getError().getCode());
 	}
 
 	@Test
 	void tools_call_unknownTool_methodNotFound() {
 		var resp = send(new McpServerConfig(), req(1, McpMethods.TOOLS_CALL, JsonMap.of("name", "missing")));
-		assertEquals(McpDispatcher.CODE_METHOD_NOT_FOUND, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_METHOD_NOT_FOUND, resp.getError().getCode());
 	}
 
 	@Test
 	void tools_call_argumentsNotObject_throwsInvalidParams() {
 		var config = new McpServerConfig().addTool(tool("e", a -> new CallToolResult()));
 		var resp = send(config, req(1, McpMethods.TOOLS_CALL, JsonMap.of("name", "e", "arguments", "string-not-map")));
-		assertEquals(McpDispatcher.CODE_INVALID_PARAMS, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INVALID_PARAMS, resp.getError().getCode());
 	}
 
 	@Test
 	void tools_call_paramsNotMap_invalidParams() {
 		var config = new McpServerConfig().addTool(tool("e", a -> new CallToolResult()));
 		var resp = send(config, req(1, McpMethods.TOOLS_CALL, "not-a-map"));
-		assertEquals(McpDispatcher.CODE_INVALID_PARAMS, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INVALID_PARAMS, resp.getError().getCode());
 	}
 
 	@Test
@@ -270,7 +270,7 @@ class McpDispatcher_Test {
 			throw new RuntimeException("boom");
 		}));
 		var resp = send(config, req(1, McpMethods.TOOLS_CALL, JsonMap.of("name", "e")));
-		assertEquals(McpDispatcher.CODE_INTERNAL_ERROR, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INTERNAL_ERROR, resp.getError().getCode());
 		assertString("boom", resp.getError().getMessage());
 	}
 
@@ -280,7 +280,7 @@ class McpDispatcher_Test {
 			throw new IllegalStateException();
 		}));
 		var resp = send(config, req(1, McpMethods.TOOLS_CALL, JsonMap.of("name", "e")));
-		assertEquals(McpDispatcher.CODE_INTERNAL_ERROR, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INTERNAL_ERROR, resp.getError().getCode());
 		assertString("IllegalStateException", resp.getError().getMessage());
 	}
 
@@ -298,13 +298,13 @@ class McpDispatcher_Test {
 	@Test
 	void prompts_get_missingName_invalidParams() {
 		var resp = send(new McpServerConfig(), req(1, McpMethods.PROMPTS_GET, JsonMap.of()));
-		assertEquals(McpDispatcher.CODE_INVALID_PARAMS, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INVALID_PARAMS, resp.getError().getCode());
 	}
 
 	@Test
 	void prompts_get_unknown_methodNotFound() {
 		var resp = send(new McpServerConfig(), req(1, McpMethods.PROMPTS_GET, JsonMap.of("name", "missing")));
-		assertEquals(McpDispatcher.CODE_METHOD_NOT_FOUND, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_METHOD_NOT_FOUND, resp.getError().getCode());
 	}
 
 	// -------- resources ---------
@@ -321,13 +321,13 @@ class McpDispatcher_Test {
 	@Test
 	void resources_read_missingUri_invalidParams() {
 		var resp = send(new McpServerConfig(), req(1, McpMethods.RESOURCES_READ, JsonMap.of()));
-		assertEquals(McpDispatcher.CODE_INVALID_PARAMS, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INVALID_PARAMS, resp.getError().getCode());
 	}
 
 	@Test
 	void resources_read_unknown_methodNotFound() {
 		var resp = send(new McpServerConfig(), req(1, McpMethods.RESOURCES_READ, JsonMap.of("uri", "ghost://")));
-		assertEquals(McpDispatcher.CODE_METHOD_NOT_FOUND, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_METHOD_NOT_FOUND, resp.getError().getCode());
 	}
 
 	// -------- pagination cursor passthrough ---------
@@ -341,12 +341,19 @@ class McpDispatcher_Test {
 		assertString("1", resp.getNextCursor());
 	}
 
-	// -------- Mcp facade ---------
+	// -------- error code table ---------
 
 	@Test
-	void facade_dispatches() {
-		var config = new McpServerConfig();
-		var resp = Mcp.handle(req(1, McpMethods.PING, null), config, ctx);
-		assertNotNull(resp);
+	void errorCode_tableIsComplete() {
+		var a = new Mcp20250618Revision(null);
+		assertEquals(-32600, a.errorCode(McpErrorKind.INVALID_REQUEST));
+		assertEquals(-32601, a.errorCode(McpErrorKind.UNKNOWN_METHOD));
+		assertEquals(-32601, a.errorCode(McpErrorKind.TOOL_NOT_FOUND), "known-wrong mapping, preserved deliberately");
+		assertEquals(-32601, a.errorCode(McpErrorKind.PROMPT_NOT_FOUND), "known-wrong mapping, preserved deliberately");
+		assertEquals(-32601, a.errorCode(McpErrorKind.RESOURCE_NOT_FOUND), "known-wrong mapping, preserved deliberately");
+		assertEquals(-32602, a.errorCode(McpErrorKind.INVALID_PARAMS));
+		assertEquals(-32603, a.errorCode(McpErrorKind.INTERNAL_ERROR));
+		assertEquals(-32700, a.errorCode(McpErrorKind.PARSE_ERROR));
+		assertEquals("2025-06-18", a.protocolVersion());
 	}
 }
diff --git a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpHandlerDefaults_Test.java b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpHandlerDefaults_Test.java
similarity index 75%
rename from juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpHandlerDefaults_Test.java
rename to juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpHandlerDefaults_Test.java
index 94f4a309cd..55b78d910e 100644
--- a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpHandlerDefaults_Test.java
+++ b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpHandlerDefaults_Test.java
@@ -14,13 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.juneau.rest.server.mcp;
+package org.apache.juneau.rest.server.mcp.v20250618;
 
 import static org.apache.juneau.test.bct.BctAssertions.*;
 import static org.junit.jupiter.api.Assertions.*;
 
 import org.apache.juneau.bean.mcp.v20250618.*;
 import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.rest.server.mcp.*;
 import org.junit.jupiter.api.*;
 
 /**
@@ -57,29 +58,4 @@ class McpHandlerDefaults_Test {
 			assertNotNull(ctor.newInstance());
 		});
 	}
-
-	@Test
-	void mcp_facade_constructor_isPrivate() {
-		assertDoesNotThrow(() -> {
-			var ctor = Mcp.class.getDeclaredConstructor();
-			ctor.setAccessible(true);
-			assertNotNull(ctor.newInstance());
-		});
-	}
-
-	@Test
-	void cursor_passes_ctx_to_strategy() {
-		// Verify ctx parameter reaches the cursor (covers the BeanStore parameter passthrough).
-		var bs = new BasicBeanStore();
-		var got = new Object[1];
-		McpCursor c = new McpCursor() {
-			@Override
-			public <T> McpPage<T> page(java.util.List<T> all, String cursor, BeanStore ctx) {
-				got[0] = ctx;
-				return new McpPage<>(all, null);
-			}
-		};
-		c.page(java.util.List.of(), null, bs);
-		assertSame(bs, got[0]);
-	}
 }
diff --git a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpRestServlet_Test.java b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpRestServlet20250618_Test.java
similarity index 93%
rename from juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpRestServlet_Test.java
rename to juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpRestServlet20250618_Test.java
index 53518668c1..ca289b1eaf 100644
--- a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpRestServlet_Test.java
+++ b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpRestServlet20250618_Test.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.juneau.rest.server.mcp;
+package org.apache.juneau.rest.server.mcp.v20250618;
 
 import static org.apache.juneau.test.bct.BctAssertions.*;
 import static org.junit.jupiter.api.Assertions.*;
@@ -28,15 +28,16 @@ import org.apache.juneau.commons.inject.*;
 import org.apache.juneau.marshall.json.*;
 import org.apache.juneau.rest.mock.classic.*;
 import org.apache.juneau.rest.server.*;
+import org.apache.juneau.rest.server.mcp.*;
 import org.junit.jupiter.api.*;
 
 /**
- * End-to-end coverage for {@link McpRestServlet} via {@link MockRestClient}.
+ * End-to-end coverage for {@link McpRestServlet20250618} via {@link MockRestClient}.
  */
 @SuppressWarnings({
 	"resource" // Test helpers return Closeables; Eclipse JDT @Owning warning is by design.
 })
-class McpRestServlet_Test extends TestBase {
+class McpRestServlet20250618_Test extends TestBase {
 
 	private static final JsonParser PAR = JsonParser.create()
 		.typePropertyName(Content.class, "type")
@@ -44,7 +45,7 @@ class McpRestServlet_Test extends TestBase {
 		.build();
 
 	@Rest(serializers = JsonSerializer.class, parsers = JsonParser.class, defaultAccept = "application/json")
-	public static class A extends McpRestServlet {
+	public static class A extends McpRestServlet20250618 {
 		private static final long serialVersionUID = 1L;
 
 		@Override
@@ -115,7 +116,7 @@ class McpRestServlet_Test extends TestBase {
 
 	@Rest(path = "/api", serializers = JsonSerializer.class, parsers = JsonParser.class, defaultAccept = "application/json")
 	@org.apache.juneau.marshall.serializer.SerializerConfig(addBeanTypes = "true")
-	public static class B extends org.apache.juneau.rest.server.servlet.BasicRestServlet implements McpEndpoint {
+	public static class B extends org.apache.juneau.rest.server.servlet.BasicRestServlet implements McpEndpoint20250618 {
 		private static final long serialVersionUID = 1L;
 
 		@Override
@@ -146,7 +147,7 @@ class McpRestServlet_Test extends TestBase {
 	// -------- failure modes --------
 
 	@Rest(serializers = JsonSerializer.class, parsers = JsonParser.class, defaultAccept = "application/json")
-	public static class C extends McpRestServlet {
+	public static class C extends McpRestServlet20250618 {
 		private static final long serialVersionUID = 1L;
 		@Override
 		protected McpServerConfig createMcpConfig() {
diff --git a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpServerConfig_Test.java b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpServerConfig_Test.java
similarity index 97%
rename from juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpServerConfig_Test.java
rename to juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpServerConfig_Test.java
index b0000ea739..155c7798a7 100644
--- a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpServerConfig_Test.java
+++ b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpServerConfig_Test.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.juneau.rest.server.mcp;
+package org.apache.juneau.rest.server.mcp.v20250618;
 
 import static org.apache.juneau.test.bct.BctAssertions.*;
 import static org.junit.jupiter.api.Assertions.*;
@@ -23,6 +23,7 @@ import java.util.*;
 
 import org.apache.juneau.bean.mcp.v20250618.*;
 import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.rest.server.mcp.*;
 import org.junit.jupiter.api.*;
 
 /**
diff --git a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpTypedHandlers_Test.java b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedHandlers_Test.java
similarity index 90%
rename from juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpTypedHandlers_Test.java
rename to juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedHandlers_Test.java
index 7c2ac864ac..72fcf1da66 100644
--- a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/McpTypedHandlers_Test.java
+++ b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedHandlers_Test.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.juneau.rest.server.mcp;
+package org.apache.juneau.rest.server.mcp.v20250618;
 
 import static org.apache.juneau.test.bct.BctAssertions.*;
 import static org.junit.jupiter.api.Assertions.*;
@@ -25,6 +25,7 @@ import org.apache.juneau.bean.jsonrpc.*;
 import org.apache.juneau.bean.mcp.v20250618.*;
 import org.apache.juneau.commons.inject.*;
 import org.apache.juneau.marshall.collections.*;
+import org.apache.juneau.rest.server.mcp.*;
 import org.junit.jupiter.api.*;
 
 /**
@@ -50,7 +51,11 @@ class McpTypedHandlers_Test {
 	}
 
 	private final BeanStore ctx = new BasicBeanStore();
-	private final McpDispatcher dispatcher = new McpDispatcher();
+	private final Mcp20250618Revision revision = new Mcp20250618Revision(null);
+
+	private JsonRpcResponse dispatch(JsonRpcRequest req, McpServerConfig config) {
+		return revision.dispatch(new McpExchange(req, n -> null), config, ctx);
+	}
 
 	@Test
 	void typedTool_argsBound_andResultWrappedAsText() {
@@ -78,7 +83,7 @@ class McpTypedHandlers_Test {
 			.setId(1)
 			.setMethod(McpMethods.TOOLS_CALL)
 			.setParams(JsonMap.of("name", "echo", "arguments", JsonMap.of("message", "hi", "repeat", 3)));
-		var resp = dispatcher.dispatch(req, config, ctx);
+		var resp = dispatch(req, config);
 		var ctr = (CallToolResult) resp.getResult();
 		var text = ((TextContent) ctr.getContent().get(0)).getText();
 		assertContains("\"text\":\"hi:3\"", text);
@@ -109,7 +114,7 @@ class McpTypedHandlers_Test {
 			.setId(1)
 			.setMethod(McpMethods.TOOLS_CALL)
 			.setParams(JsonMap.of("name", "d"));
-		var resp = dispatcher.dispatch(req, config, ctx);
+		var resp = dispatch(req, config);
 		assertSame(ctr, resp.getResult());
 	}
 
@@ -124,9 +129,9 @@ class McpTypedHandlers_Test {
 			public String call(EchoArgs args, BeanStore ctx) { return "hello"; }
 		};
 		var config = new McpServerConfig().addTool(McpTypedHandlers.adaptTool(typed));
-		var resp = dispatcher.dispatch(new JsonRpcRequest()
+		var resp = dispatch(new JsonRpcRequest()
 			.setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
-			.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "s")), config, ctx);
+			.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "s")), config);
 		var ctr = (CallToolResult) resp.getResult();
 		assertString("hello", ((TextContent) ctr.getContent().get(0)).getText());
 	}
@@ -142,9 +147,9 @@ class McpTypedHandlers_Test {
 			public EchoResult call(EchoArgs args, BeanStore ctx) { return null; }
 		};
 		var config = new McpServerConfig().addTool(McpTypedHandlers.adaptTool(typed));
-		var resp = dispatcher.dispatch(new JsonRpcRequest()
+		var resp = dispatch(new JsonRpcRequest()
 			.setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
-			.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "n")), config, ctx);
+			.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "n")), config);
 		var ctr = (CallToolResult) resp.getResult();
 		assertString("", ((TextContent) ctr.getContent().get(0)).getText());
 	}
@@ -162,9 +167,9 @@ class McpTypedHandlers_Test {
 			}
 		};
 		var config = new McpServerConfig().addTool(McpTypedHandlers.adaptTool(typed));
-		var resp = dispatcher.dispatch(new JsonRpcRequest()
+		var resp = dispatch(new JsonRpcRequest()
 			.setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
-			.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "z")), config, ctx);
+			.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "z")), config);
 		var ctr = (CallToolResult) resp.getResult();
 		assertString("null", ((TextContent) ctr.getContent().get(0)).getText());
 	}
@@ -181,11 +186,11 @@ class McpTypedHandlers_Test {
 		};
 		var config = new McpServerConfig().addTool(McpTypedHandlers.adaptTool(typed));
 		// Bad: 'repeat' should be int, supply a non-numeric value to trigger parser failure.
-		var resp = dispatcher.dispatch(new JsonRpcRequest()
+		var resp = dispatch(new JsonRpcRequest()
 			.setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
 			.setMethod(McpMethods.TOOLS_CALL)
-			.setParams(JsonMap.of("name", "x", "arguments", JsonMap.of("repeat", "not-an-int"))), config, ctx);
-		assertEquals(McpDispatcher.CODE_INVALID_PARAMS, resp.getError().getCode());
+			.setParams(JsonMap.of("name", "x", "arguments", JsonMap.of("repeat", "not-an-int"))), config);
+		assertEquals(Mcp20250618Revision.CODE_INVALID_PARAMS, resp.getError().getCode());
 	}
 
 	@Test
@@ -202,9 +207,9 @@ class McpTypedHandlers_Test {
 		};
 		var raw = McpTypedHandlers.adaptPrompt(typed);
 		var config = new McpServerConfig().addPrompt(raw);
-		var resp = dispatcher.dispatch(new JsonRpcRequest()
+		var resp = dispatch(new JsonRpcRequest()
 			.setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
-			.setMethod(McpMethods.PROMPTS_GET).setParams(JsonMap.of("name", "p")), config, ctx);
+			.setMethod(McpMethods.PROMPTS_GET).setParams(JsonMap.of("name", "p")), config);
 		var pr = (GetPromptResult) resp.getResult();
 		assertString("null", pr.getDescription());
 	}
@@ -260,10 +265,10 @@ class McpTypedHandlers_Test {
 			public Unserializable call(EchoArgs args, BeanStore ctx) { return new Unserializable(); }
 		};
 		var config = new McpServerConfig().addTool(McpTypedHandlers.adaptTool(typed));
-		var resp = dispatcher.dispatch(new JsonRpcRequest()
+		var resp = dispatch(new JsonRpcRequest()
 			.setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
-			.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "u")), config, ctx);
-		assertEquals(McpDispatcher.CODE_INTERNAL_ERROR, resp.getError().getCode());
+			.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "u")), config);
+		assertEquals(Mcp20250618Revision.CODE_INTERNAL_ERROR, resp.getError().getCode());
 	}
 
 	@Test
@@ -280,10 +285,10 @@ class McpTypedHandlers_Test {
 		};
 		var raw = McpTypedHandlers.adaptPrompt(typed);
 		var config = new McpServerConfig().addPrompt(raw);
-		var resp = dispatcher.dispatch(new JsonRpcRequest()
+		var resp = dispatch(new JsonRpcRequest()
 			.setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
 			.setMethod(McpMethods.PROMPTS_GET)
-			.setParams(JsonMap.of("name", "p", "arguments", JsonMap.of("message", "hello"))), config, ctx);
+			.setParams(JsonMap.of("name", "p", "arguments", JsonMap.of("message", "hello"))), config);
 		var pr = (GetPromptResult) resp.getResult();
 		assertString("hello", pr.getDescription());
 	}
diff --git a/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/Mcp20250618Revision.java b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/Mcp20250618Revision.java
new file mode 100644
index 0000000000..09da0a545c
--- /dev/null
+++ b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/Mcp20250618Revision.java
@@ -0,0 +1,230 @@
+/*
+ * 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.v20250618;
+
+import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.Shorts.*;
+import static org.apache.juneau.commons.utils.StringUtils.*;
+
+import java.util.*;
+
+import org.apache.juneau.bean.jsonrpc.*;
+import org.apache.juneau.bean.mcp.v20250618.*;
+import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.marshall.collections.*;
+import org.apache.juneau.rest.server.mcp.*;
+
+/**
+ * {@link McpRevision} implementation for MCP revision {@code 2025-06-18}.
+ *
+ * <p>
+ * Owns this revision's JSON-RPC method table and error-code table. Note the error-code table
+ * deliberately reproduces a known-wrong mapping: {@link McpErrorKind#UNKNOWN_METHOD},
+ * {@link McpErrorKind#TOOL_NOT_FOUND}, {@link McpErrorKind#PROMPT_NOT_FOUND} and
+ * {@link McpErrorKind#RESOURCE_NOT_FOUND} all report {@code -32601}, which is only actually correct
+ * for the first. Preserving that is intentional — the corrective fix is tracked separately and is
+ * not part of this re-layering.
+ *
+ * <p>
+ * <b>Constructed per binding, not shared as a singleton (Correction C8).</b> The bound servlet or
+ * mixin builds a fresh instance on every {@code revision()} call, passing its own
+ * {@code capabilities()} hook result into the constructor — see {@link McpRestServlet20250618} and
+ * {@link McpEndpoint20250618}. That is how this revision learns which explicit capabilities (if any)
+ * to advertise, with no channel back into core: this class is otherwise entirely stateless, so
+ * constructing a new one per call costs nothing.
+ */
+public final class Mcp20250618Revision implements McpRevision {
+
+	private final ServerCapabilities capabilities;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param capabilities Explicit capabilities to advertise on {@code initialize}, or <jk>null</jk>
+	 * 	to auto-derive from the registered tool/prompt/resource lists (today's default behavior).
+	 */
+	public Mcp20250618Revision(ServerCapabilities capabilities) {
+		this.capabilities = capabilities;
+	}
+
+	/** JSON-RPC error code: parse error. Never reported by this revision (see {@link McpErrorKind#PARSE_ERROR}). */
+	public static final int CODE_PARSE_ERROR = -32700;
+
+	/** JSON-RPC error code: invalid request. */
+	public static final int CODE_INVALID_REQUEST = -32600;
+
+	/** JSON-RPC error code: method not found. */
+	public static final int CODE_METHOD_NOT_FOUND = -32601;
+
+	/** JSON-RPC error code: invalid params. */
+	public static final int CODE_INVALID_PARAMS = -32602;
+
+	/** JSON-RPC error code: internal error. */
+	public static final int CODE_INTERNAL_ERROR = -32603;
+
+	/** Default server name reported by {@code initialize} when the config supplies no server identity. */
+	public static final String DEFAULT_SERVER_NAME = "juneau-rest-server-mcp";
+
+	@Override /* McpRevision */
+	public String protocolVersion() {
+		return McpProtocol.VERSION_2025_06_18;
+	}
+
+	@Override /* McpRevision */
+	public int errorCode(McpErrorKind kind) {
+		return switch (kind) {
+			case INVALID_REQUEST -> CODE_INVALID_REQUEST;
+			case UNKNOWN_METHOD, TOOL_NOT_FOUND, PROMPT_NOT_FOUND, RESOURCE_NOT_FOUND -> CODE_METHOD_NOT_FOUND;
+			case INVALID_PARAMS -> CODE_INVALID_PARAMS;
+			case INTERNAL_ERROR -> CODE_INTERNAL_ERROR;
+			case PARSE_ERROR -> CODE_PARSE_ERROR;
+		};
+	}
+
+	@Override /* McpRevision */
+	public JsonRpcResponse dispatch(McpExchange exchange, McpServerConfig config, BeanStore ctx) {
+		assertArgNotNull("exchange", exchange);
+		assertArgNotNull("config", config);
+		assertArgNotNull("ctx", ctx);
+
+		var req = exchange.request();
+		if (req == null)
+			return JsonRpcResponse.errorResponse(null, errorCode(McpErrorKind.INVALID_REQUEST), "Request envelope is null");
+
+		var id = req.getId();
+		var method = req.getMethod();
+
+		if (isEmpty(method))
+			return JsonRpcResponse.notification(id) ? null
+				: JsonRpcResponse.errorResponse(id, errorCode(McpErrorKind.INVALID_REQUEST), "Missing method");
+
+		try {
+			var result = invoke(method, req.getParams(), config, ctx);
+			return JsonRpcResponse.notification(id) ? null : JsonRpcResponse.ok(id, result);
+		} catch (McpException e) {
+			return JsonRpcResponse.notification(id) ? null : new JsonRpcResponse()
+				.setJsonrpc(McpProtocol.JSON_RPC_2_0)
+				.setId(id)
+				.setError(e.toJsonRpcError());
+		} catch (Exception e) {
+			if (JsonRpcResponse.notification(id))
+				return null;
+			var message = e.getMessage() == null ? cns(e) : e.getMessage();
+			return JsonRpcResponse.errorResponse(id, errorCode(McpErrorKind.INTERNAL_ERROR), message, JsonMap.of("type", cn(e)));
+		}
+	}
+
+	private Object invoke(String method, Object params, McpServerConfig config, BeanStore ctx) {
+		switch (method) {
+			case McpMethods.INITIALIZE:
+				return initialize(config);
+			case McpMethods.PING:
+				return new JsonMap();
+			case McpMethods.TOOLS_LIST:
+				return listTools(config, params, ctx);
+			case McpMethods.TOOLS_CALL:
+				return callTool(config, params, ctx);
+			case McpMethods.PROMPTS_LIST:
+				return listPrompts(config, params, ctx);
+			case McpMethods.PROMPTS_GET:
+				return getPrompt(config, params, ctx);
+			case McpMethods.RESOURCES_LIST:
+				return listResources(config, params, ctx);
+			case McpMethods.RESOURCES_READ:
+				return readResource(config, params, ctx);
+			default:
+				throw new McpException(errorCode(McpErrorKind.UNKNOWN_METHOD), "Method not found: " + method);
+		}
+	}
+
+	private InitializeResult initialize(McpServerConfig config) {
+		var caps = config.getCapabilities();
+		if (caps == null) {
+			caps = new ServerCapabilities();
+			if (! config.getTools().isEmpty())
+				caps.setTools(new ToolCapability());
+			if (! config.getPrompts().isEmpty())
+				caps.setPrompts(new PromptCapability());
+			if (! config.getResources().isEmpty())
+				caps.setResources(new ResourceCapability());
+		}
+		var info = config.getServerInfo();
+		if (info == null)
+			info = new Implementation().setName(DEFAULT_SERVER_NAME).setVersion("unknown");
+		return new InitializeResult()
+			.setProtocolVersion(config.getProtocolVersion())
+			.setCapabilities(caps)
+			.setServerInfo(info)
+			.setInstructions(config.getInstructions());
+	}
+
+	private static ListToolsResult listTools(McpServerConfig config, Object params, BeanStore ctx) {
+		var descriptors = config.getTools().stream().map(McpToolHandler::descriptor).toList();
+		var page = config.getCursor().page(descriptors, McpCursor.cursorOf(params), ctx);
+		return new ListToolsResult().setTools(page.items()).setNextCursor(page.nextCursor());
+	}
+
+	private CallToolResult callTool(McpServerConfig config, Object params, BeanStore ctx) {
+		var p = McpParamUtils.asMap(params);
+		var name = McpParamUtils.strParam(p, "name");
+		if (name == null)
+			throw new McpException(errorCode(McpErrorKind.INVALID_PARAMS), "Missing tool name");
+		var handler = config.getTools().stream()
+			.filter(h -> name.equals(h.descriptor().getName()))
+			.findFirst()
+			.orElseThrow(() -> new McpException(errorCode(McpErrorKind.TOOL_NOT_FOUND), "Tool not found: " + name));
+		var args = McpParamUtils.mapParam(p, "arguments");
+		return handler.call(args, ctx);
+	}
+
+	private static ListPromptsResult listPrompts(McpServerConfig config, Object params, BeanStore ctx) {
+		var descriptors = config.getPrompts().stream().map(McpPromptHandler::descriptor).toList();
+		var page = config.getCursor().page(descriptors, McpCursor.cursorOf(params), ctx);
+		return new ListPromptsResult().setPrompts(page.items()).setNextCursor(page.nextCursor());
+	}
+
+	private GetPromptResult getPrompt(McpServerConfig config, Object params, BeanStore ctx) {
+		var p = McpParamUtils.asMap(params);
+		var name = McpParamUtils.strParam(p, "name");
+		if (name == null)
+			throw new McpException(errorCode(McpErrorKind.INVALID_PARAMS), "Missing prompt name");
+		var handler = config.getPrompts().stream()
+			.filter(h -> name.equals(h.descriptor().getName()))
+			.findFirst()
+			.orElseThrow(() -> new McpException(errorCode(McpErrorKind.PROMPT_NOT_FOUND), "Prompt not found: " + name));
+		var args = McpParamUtils.mapParam(p, "arguments");
+		return handler.get(args, ctx);
+	}
+
+	private static ListResourcesResult listResources(McpServerConfig config, Object params, BeanStore ctx) {
+		var descriptors = config.getResources().stream().map(McpResourceHandler::descriptor).toList();
+		var page = config.getCursor().page(descriptors, McpCursor.cursorOf(params), ctx);
+		return new ListResourcesResult().setResources(page.items()).setNextCursor(page.nextCursor());
+	}
+
+	private ReadResourceResult readResource(McpServerConfig config, Object params, BeanStore ctx) {
+		var p = McpParamUtils.asMap(params);
+		var uri = McpParamUtils.strParam(p, "uri");
+		if (uri == null)
+			throw new McpException(errorCode(McpErrorKind.INVALID_PARAMS), "Missing resource uri");
+		var handler = config.getResources().stream()
+			.filter(h -> uri.equals(h.descriptor().getUri()))
+			.findFirst()
+			.orElseThrow(() -> new McpException(errorCode(McpErrorKind.RESOURCE_NOT_FOUND), "Resource not found: " + uri));
+		return handler.read(uri, ctx);
+	}
+}
diff --git a/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/McpEndpoint20250618.java b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/McpEndpoint20250618.java
new file mode 100644
index 0000000000..bcc0134840
--- /dev/null
+++ b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/McpEndpoint20250618.java
@@ -0,0 +1,66 @@
+/*
+ * 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.v20250618;
+
+import org.apache.juneau.bean.mcp.v20250618.*;
+import org.apache.juneau.rest.server.mcp.*;
+
+/**
+ * Mixin interface that exposes a {@code 2025-06-18} MCP endpoint at {@code POST /mcp} on any Juneau
+ * REST resource.
+ *
+ * <p>
+ * The mixin path and the servlet-subclass path
+ * ({@link McpRestServlet20250618}) are at parity: both bind the revision at compile time and both
+ * expose the same {@link #capabilities()} hook.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <pre>
+ * @Rest(path="/api")
+ * public class MyResource extends BasicRestServlet implements McpEndpoint20250618 {
+ *     @Override
+ *     public McpServerConfig getMcpConfig() {
+ *         return new McpServerConfig().addTool(new MyEchoTool());
+ *     }
+ * }
+ * </pre>
+ */
+public interface McpEndpoint20250618 extends McpEndpoint {
+
+	@Override /* McpEndpoint */
+	default McpRevision revision() {
+		return new Mcp20250618Revision(capabilities());
+	}
+
+	/**
+	 * Optional explicit capabilities advertisement for {@code initialize}.
+	 *
+	 * <p>
+	 * Returning <jk>null</jk> (the default) leaves capabilities auto-derived from the registered
+	 * tool / prompt / resource lists; a non-<jk>null</jk> value bypasses auto-derivation.
+	 *
+	 * <p>
+	 * Stays a {@code default} interface method (Correction C8), for the same reason
+	 * {@link McpRestServlet20250618#capabilities()} stays {@code protected}: only {@link #revision()}
+	 * above, declared on this same interface, ever calls it.
+	 *
+	 * @return The explicit capabilities, or <jk>null</jk> to auto-derive.
+	 */
+	default ServerCapabilities capabilities() {
+		return null;
+	}
+}
diff --git a/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/McpRestServlet20250618.java b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/McpRestServlet20250618.java
new file mode 100644
index 0000000000..ab83cfbfee
--- /dev/null
+++ b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/McpRestServlet20250618.java
@@ -0,0 +1,75 @@
+/*
+ * 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.v20250618;
+
+import org.apache.juneau.bean.mcp.v20250618.*;
+import org.apache.juneau.rest.server.mcp.*;
+
+/**
+ * Abstract MCP servlet bound to protocol revision {@code 2025-06-18}.
+ *
+ * <p>
+ * Subclass this (rather than {@link McpRestServlet}) to expose a {@code 2025-06-18} endpoint; the
+ * revision binding is a compile-time choice made by which class you extend.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <pre>
+ * @Rest(path="/mcp")
+ * public class MyMcpServlet extends McpRestServlet20250618 {
+ *     @Override
+ *     protected McpServerConfig createMcpConfig() {
+ *         return new McpServerConfig().setName("my-server").setVersion("1.0.0").addTool(new MyEchoTool());
+ *     }
+ * }
+ * </pre>
+ *
+ * @serial exclude
+ */
+public abstract class McpRestServlet20250618 extends McpRestServlet {
+	private static final long serialVersionUID = 1L;
+
+	@Override /* McpRestServlet */
+	protected McpRevision revision() {
+		return new Mcp20250618Revision(capabilities());
+	}
+
+	/**
+	 * Optional explicit capabilities advertisement for {@code initialize}.
+	 *
+	 * <p>
+	 * Returning <jk>null</jk> (the default) leaves capabilities auto-derived from the registered
+	 * tool / prompt / resource lists. Returning a non-<jk>null</jk> value bypasses auto-derivation
+	 * entirely and advertises exactly what is returned — the way to advertise
+	 * {@code resources.subscribe}, {@code logging}, {@code listChanged}, or {@code experimental},
+	 * none of which are derivable from a handler registry.
+	 *
+	 * <p>
+	 * This hook is typed against this revision's wire beans on purpose: capabilities diverge between
+	 * MCP revisions, so a neutral carrier in the core would accept data that is nonsense for the
+	 * bound revision and fail only at serialization time.
+	 *
+	 * <p>
+	 * Stays {@code protected} (Correction C8): {@link #revision()} above is the only caller, and it
+	 * lives in this same class, so there is never a need to reach this hook from outside this
+	 * class's own hierarchy — in particular, {@code Mcp20250618Revision} never calls back into it.
+	 *
+	 * @return The explicit capabilities, or <jk>null</jk> to auto-derive.
+	 */
+	protected ServerCapabilities capabilities() {
+		return null;
+	}
+}
diff --git a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpTypedHandlers.java b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedHandlers.java
similarity index 91%
rename from juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpTypedHandlers.java
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedHandlers.java
index 82146efe5d..1d24b73a35 100644
--- a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpTypedHandlers.java
+++ b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedHandlers.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.juneau.rest.server.mcp;
+package org.apache.juneau.rest.server.mcp.v20250618;
 
 import static org.apache.juneau.commons.utils.AssertionUtils.*;
 
@@ -24,10 +24,11 @@ import org.apache.juneau.bean.jsonrpc.*;
 import org.apache.juneau.bean.mcp.v20250618.*;
 import org.apache.juneau.commons.inject.*;
 import org.apache.juneau.marshall.marshaller.*;
+import org.apache.juneau.rest.server.mcp.*;
 
 /**
  * Adapters that convert {@link McpTypedToolHandler} / {@link McpTypedPromptHandler} into the raw
- * {@link McpToolHandler} / {@link McpPromptHandler} interfaces consumed by {@link McpDispatcher}.
+ * {@link McpToolHandler} / {@link McpPromptHandler} interfaces consumed by {@link Mcp20250618Revision}.
  */
 public final class McpTypedHandlers {
 
@@ -102,7 +103,7 @@ public final class McpTypedHandlers {
 			var json = Json.of(arguments);
 			return Json.to(json, type);
 		} catch (Exception e) {
-			throw new McpException(McpDispatcher.CODE_INVALID_PARAMS, "Failed to bind arguments to " + type.getName() + ": " + e.getMessage());
+			throw new McpException(Mcp20250618Revision.CODE_INVALID_PARAMS, "Failed to bind arguments to " + type.getName() + ": " + e.getMessage());
 		}
 	}
 
@@ -118,7 +119,7 @@ public final class McpTypedHandlers {
 			try {
 				text = Json.of(result);
 			} catch (Exception e) {
-				throw new McpException(McpDispatcher.CODE_INTERNAL_ERROR, "Failed to serialize tool result: " + e.getMessage());
+				throw new McpException(Mcp20250618Revision.CODE_INTERNAL_ERROR, "Failed to serialize tool result: " + e.getMessage());
 			}
 		}
 		return new CallToolResult().setContent(List.of(new TextContent().setText(text)));
diff --git a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpTypedPromptHandler.java b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedPromptHandler.java
similarity index 94%
copy from juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpTypedPromptHandler.java
copy to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedPromptHandler.java
index 524737d38a..ff1686f7c7 100644
--- a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpTypedPromptHandler.java
+++ b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedPromptHandler.java
@@ -14,10 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.juneau.rest.server.mcp;
+package org.apache.juneau.rest.server.mcp.v20250618;
 
 import org.apache.juneau.bean.mcp.v20250618.*;
 import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.rest.server.mcp.*;
 
 /**
  * Typed variant of {@link McpPromptHandler} where MCP {@code prompts/get} arguments bind into a Juneau bean.
diff --git a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpTypedToolHandler.java b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedToolHandler.java
similarity index 95%
rename from juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpTypedToolHandler.java
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedToolHandler.java
index 6db25a864d..23e4f69d77 100644
--- a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpTypedToolHandler.java
+++ b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/main/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedToolHandler.java
@@ -14,10 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.juneau.rest.server.mcp;
+package org.apache.juneau.rest.server.mcp.v20250618;
 
 import org.apache.juneau.bean.mcp.v20250618.*;
 import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.rest.server.mcp.*;
 
 /**
  * Typed variant of {@link McpToolHandler} where MCP {@code tools/call} arguments bind into a Juneau bean.
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/Characterization_Test.java b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/Characterization_Test.java
similarity index 94%
rename from juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/Characterization_Test.java
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/Characterization_Test.java
index 7b5a597526..207f4ed696 100644
--- a/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/Characterization_Test.java
+++ b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/Characterization_Test.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.juneau.rest.server.mcp;
+package org.apache.juneau.rest.server.mcp.v20250618;
 
 import static org.junit.jupiter.api.Assertions.*;
 
@@ -29,6 +29,7 @@ import org.apache.juneau.marshall.collections.*;
 import org.apache.juneau.marshall.json.*;
 import org.apache.juneau.rest.mock.classic.*;
 import org.apache.juneau.rest.server.*;
+import org.apache.juneau.rest.server.mcp.*;
 import org.junit.jupiter.params.*;
 import org.junit.jupiter.params.provider.*;
 
@@ -44,7 +45,7 @@ import org.junit.jupiter.params.provider.*;
  * <p>
  * Regenerate the {@code *.response.json} files (only ever against known-good code) with:
  * <p>
- * {@code mvn test -Drat.skip=true -pl juneau-rest/juneau-rest-server-mcp -Dtest=Characterization_Test -Djuneau.mcp.characterization.write=true}
+ * {@code mvn test -Drat.skip=true -pl juneau-rest/juneau-rest-server-mcp-2025-06-18 -Dtest=Characterization_Test -Djuneau.mcp.characterization.write=true}
  */
 @SuppressWarnings({
 	"resource" // MockRestClient is a Closeable test helper; lifetime is bounded by the test method.
@@ -57,13 +58,13 @@ class Characterization_Test {
 	// --- fixture servlets -------------------------------------------------------------------
 
 	@Rest(serializers = JsonSerializer.class, parsers = JsonParser.class, defaultAccept = "application/json")
-	public static class F_Empty extends McpRestServlet {
+	public static class F_Empty extends McpRestServlet20250618 {
 		private static final long serialVersionUID = 1L;
 		@Override protected McpServerConfig createMcpConfig() { return new McpServerConfig(); }
 	}
 
 	@Rest(serializers = JsonSerializer.class, parsers = JsonParser.class, defaultAccept = "application/json")
-	public static class F_Full extends McpRestServlet {
+	public static class F_Full extends McpRestServlet20250618 {
 		private static final long serialVersionUID = 1L;
 		@Override protected McpServerConfig createMcpConfig() {
 			return new McpServerConfig()
@@ -86,7 +87,7 @@ class Characterization_Test {
 	}
 
 	@Rest(serializers = JsonSerializer.class, parsers = JsonParser.class, defaultAccept = "application/json")
-	public static class F_Caps extends McpRestServlet {
+	public static class F_Caps extends McpRestServlet20250618 {
 		private static final long serialVersionUID = 1L;
 		@Override protected McpServerConfig createMcpConfig() {
 			return new McpServerConfig()
@@ -98,7 +99,7 @@ class Characterization_Test {
 	}
 
 	@Rest(serializers = JsonSerializer.class, parsers = JsonParser.class, defaultAccept = "application/json")
-	public static class F_Paged extends McpRestServlet {
+	public static class F_Paged extends McpRestServlet20250618 {
 		private static final long serialVersionUID = 1L;
 		@Override protected McpServerConfig createMcpConfig() {
 			return new McpServerConfig()
@@ -109,7 +110,7 @@ class Characterization_Test {
 	}
 
 	@Rest(serializers = JsonSerializer.class, parsers = JsonParser.class, defaultAccept = "application/json")
-	public static class F_Throw extends McpRestServlet {
+	public static class F_Throw extends McpRestServlet20250618 {
 		private static final long serialVersionUID = 1L;
 		@Override protected McpServerConfig createMcpConfig() {
 			return new McpServerConfig()
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpDispatcher_Test.java b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/Mcp20250618Revision_Test.java
similarity index 82%
rename from juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpDispatcher_Test.java
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/Mcp20250618Revision_Test.java
index 0120bc06ab..fda7de737f 100644
--- a/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpDispatcher_Test.java
+++ b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/Mcp20250618Revision_Test.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.juneau.rest.server.mcp;
+package org.apache.juneau.rest.server.mcp.v20250618;
 
 import static org.apache.juneau.test.bct.BctAssertions.*;
 import static org.junit.jupiter.api.Assertions.*;
@@ -25,14 +25,14 @@ import org.apache.juneau.bean.jsonrpc.*;
 import org.apache.juneau.bean.mcp.v20250618.*;
 import org.apache.juneau.commons.inject.*;
 import org.apache.juneau.marshall.collections.*;
+import org.apache.juneau.rest.server.mcp.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Coverage for {@link McpDispatcher}.
+ * Coverage for {@link Mcp20250618Revision}.
  */
-class McpDispatcher_Test {
+class Mcp20250618Revision_Test {
 
-	private final McpDispatcher dispatcher = new McpDispatcher();
 	private final BeanStore ctx = new BasicBeanStore();
 
 	private static McpToolHandler tool(String name, java.util.function.Function<Map<String,Object>,CallToolResult> fn) {
@@ -82,7 +82,7 @@ class McpDispatcher_Test {
 	}
 
 	private JsonRpcResponse send(McpServerConfig config, JsonRpcRequest r) {
-		return dispatcher.dispatch(r, config, ctx);
+		return new Mcp20250618Revision(null).dispatch(new McpExchange(r, n -> null), config, ctx);
 	}
 
 	@Test
@@ -99,7 +99,7 @@ class McpDispatcher_Test {
 		assertNotNull(result.getCapabilities().getTools());
 		assertNotNull(result.getCapabilities().getPrompts());
 		assertNotNull(result.getCapabilities().getResources());
-		assertString(McpDispatcher.DEFAULT_SERVER_NAME, result.getServerInfo().getName());
+		assertString(Mcp20250618Revision.DEFAULT_SERVER_NAME, result.getServerInfo().getName());
 	}
 
 	@Test
@@ -123,19 +123,19 @@ class McpDispatcher_Test {
 	@Test
 	void a04_method_notFound() {
 		var resp = send(new McpServerConfig(), req(1, "no/such/method", null));
-		assertEquals(McpDispatcher.CODE_METHOD_NOT_FOUND, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_METHOD_NOT_FOUND, resp.getError().getCode());
 	}
 
 	@Test
 	void a05_missing_method() {
 		var resp = send(new McpServerConfig(), req(1, null, null));
-		assertEquals(McpDispatcher.CODE_INVALID_REQUEST, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INVALID_REQUEST, resp.getError().getCode());
 	}
 
 	@Test
 	void a06_empty_method_string() {
 		var resp = send(new McpServerConfig(), req(1, "", null));
-		assertEquals(McpDispatcher.CODE_INVALID_REQUEST, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INVALID_REQUEST, resp.getError().getCode());
 	}
 
 	@Test
@@ -183,8 +183,8 @@ class McpDispatcher_Test {
 
 	@Test
 	void a08_nullEnvelope_returnsInvalidRequest() {
-		var resp = dispatcher.dispatch(null, new McpServerConfig(), ctx);
-		assertEquals(McpDispatcher.CODE_INVALID_REQUEST, resp.getError().getCode());
+		var resp = new Mcp20250618Revision(null).dispatch(new McpExchange(null, n -> null), new McpServerConfig(), ctx);
+		assertEquals(Mcp20250618Revision.CODE_INVALID_REQUEST, resp.getError().getCode());
 	}
 
 	// -------- tools/list ---------
@@ -230,27 +230,27 @@ class McpDispatcher_Test {
 	@Test
 	void d02_tools_call_missingName_invalidParams() {
 		var resp = send(new McpServerConfig(), req(1, McpMethods.TOOLS_CALL, JsonMap.of("arguments", JsonMap.of())));
-		assertEquals(McpDispatcher.CODE_INVALID_PARAMS, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INVALID_PARAMS, resp.getError().getCode());
 	}
 
 	@Test
 	void d03_tools_call_unknownTool_methodNotFound() {
 		var resp = send(new McpServerConfig(), req(1, McpMethods.TOOLS_CALL, JsonMap.of("name", "missing")));
-		assertEquals(McpDispatcher.CODE_METHOD_NOT_FOUND, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_METHOD_NOT_FOUND, resp.getError().getCode());
 	}
 
 	@Test
 	void d04_tools_call_argumentsNotObject_throwsInvalidParams() {
 		var config = new McpServerConfig().addTool(tool("e", a -> new CallToolResult()));
 		var resp = send(config, req(1, McpMethods.TOOLS_CALL, JsonMap.of("name", "e", "arguments", "string-not-map")));
-		assertEquals(McpDispatcher.CODE_INVALID_PARAMS, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INVALID_PARAMS, resp.getError().getCode());
 	}
 
 	@Test
 	void d05_tools_call_paramsNotMap_invalidParams() {
 		var config = new McpServerConfig().addTool(tool("e", a -> new CallToolResult()));
 		var resp = send(config, req(1, McpMethods.TOOLS_CALL, "not-a-map"));
-		assertEquals(McpDispatcher.CODE_INVALID_PARAMS, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INVALID_PARAMS, resp.getError().getCode());
 	}
 
 	@Test
@@ -270,7 +270,7 @@ class McpDispatcher_Test {
 			throw new RuntimeException("boom");
 		}));
 		var resp = send(config, req(1, McpMethods.TOOLS_CALL, JsonMap.of("name", "e")));
-		assertEquals(McpDispatcher.CODE_INTERNAL_ERROR, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INTERNAL_ERROR, resp.getError().getCode());
 		assertString("boom", resp.getError().getMessage());
 	}
 
@@ -280,7 +280,7 @@ class McpDispatcher_Test {
 			throw new IllegalStateException();
 		}));
 		var resp = send(config, req(1, McpMethods.TOOLS_CALL, JsonMap.of("name", "e")));
-		assertEquals(McpDispatcher.CODE_INTERNAL_ERROR, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INTERNAL_ERROR, resp.getError().getCode());
 		assertString("IllegalStateException", resp.getError().getMessage());
 	}
 
@@ -298,13 +298,13 @@ class McpDispatcher_Test {
 	@Test
 	void e02_prompts_get_missingName_invalidParams() {
 		var resp = send(new McpServerConfig(), req(1, McpMethods.PROMPTS_GET, JsonMap.of()));
-		assertEquals(McpDispatcher.CODE_INVALID_PARAMS, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INVALID_PARAMS, resp.getError().getCode());
 	}
 
 	@Test
 	void e03_prompts_get_unknown_methodNotFound() {
 		var resp = send(new McpServerConfig(), req(1, McpMethods.PROMPTS_GET, JsonMap.of("name", "missing")));
-		assertEquals(McpDispatcher.CODE_METHOD_NOT_FOUND, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_METHOD_NOT_FOUND, resp.getError().getCode());
 	}
 
 	// -------- resources ---------
@@ -321,13 +321,13 @@ class McpDispatcher_Test {
 	@Test
 	void f02_resources_read_missingUri_invalidParams() {
 		var resp = send(new McpServerConfig(), req(1, McpMethods.RESOURCES_READ, JsonMap.of()));
-		assertEquals(McpDispatcher.CODE_INVALID_PARAMS, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_INVALID_PARAMS, resp.getError().getCode());
 	}
 
 	@Test
 	void f03_resources_read_unknown_methodNotFound() {
 		var resp = send(new McpServerConfig(), req(1, McpMethods.RESOURCES_READ, JsonMap.of("uri", "ghost://")));
-		assertEquals(McpDispatcher.CODE_METHOD_NOT_FOUND, resp.getError().getCode());
+		assertEquals(Mcp20250618Revision.CODE_METHOD_NOT_FOUND, resp.getError().getCode());
 	}
 
 	// -------- pagination cursor passthrough ---------
@@ -341,12 +341,19 @@ class McpDispatcher_Test {
 		assertString("1", resp.getNextCursor());
 	}
 
-	// -------- Mcp facade ---------
+	// -------- error code table ---------
 
 	@Test
-	void h01_facade_dispatches() {
-		var config = new McpServerConfig();
-		var resp = Mcp.handle(req(1, McpMethods.PING, null), config, ctx);
-		assertNotNull(resp);
+	void h01_errorCode_tableIsComplete() {
+		var a = new Mcp20250618Revision(null);
+		assertEquals(-32600, a.errorCode(McpErrorKind.INVALID_REQUEST));
+		assertEquals(-32601, a.errorCode(McpErrorKind.UNKNOWN_METHOD));
+		assertEquals(-32601, a.errorCode(McpErrorKind.TOOL_NOT_FOUND), "known-wrong mapping, preserved deliberately");
+		assertEquals(-32601, a.errorCode(McpErrorKind.PROMPT_NOT_FOUND), "known-wrong mapping, preserved deliberately");
+		assertEquals(-32601, a.errorCode(McpErrorKind.RESOURCE_NOT_FOUND), "known-wrong mapping, preserved deliberately");
+		assertEquals(-32602, a.errorCode(McpErrorKind.INVALID_PARAMS));
+		assertEquals(-32603, a.errorCode(McpErrorKind.INTERNAL_ERROR));
+		assertEquals(-32700, a.errorCode(McpErrorKind.PARSE_ERROR));
+		assertEquals("2025-06-18", a.protocolVersion());
 	}
 }
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpHandlerDefaults_Test.java b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpHandlerDefaults_Test.java
similarity index 75%
rename from juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpHandlerDefaults_Test.java
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpHandlerDefaults_Test.java
index 5a623eda45..dbbb8e883e 100644
--- a/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpHandlerDefaults_Test.java
+++ b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpHandlerDefaults_Test.java
@@ -14,13 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.juneau.rest.server.mcp;
+package org.apache.juneau.rest.server.mcp.v20250618;
 
 import static org.apache.juneau.test.bct.BctAssertions.*;
 import static org.junit.jupiter.api.Assertions.*;
 
 import org.apache.juneau.bean.mcp.v20250618.*;
 import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.rest.server.mcp.*;
 import org.junit.jupiter.api.*;
 
 /**
@@ -57,29 +58,4 @@ class McpHandlerDefaults_Test {
 			assertNotNull(ctor.newInstance());
 		});
 	}
-
-	@Test
-	void c02_mcp_facade_constructor_isPrivate() {
-		assertDoesNotThrow(() -> {
-			var ctor = Mcp.class.getDeclaredConstructor();
-			ctor.setAccessible(true);
-			assertNotNull(ctor.newInstance());
-		});
-	}
-
-	@Test
-	void d01_cursor_passes_ctx_to_strategy() {
-		// Verify ctx parameter reaches the cursor (covers the BeanStore parameter passthrough).
-		var bs = new BasicBeanStore();
-		var got = new Object[1];
-		McpCursor c = new McpCursor() {
-			@Override
-			public <T> McpPage<T> page(java.util.List<T> all, String cursor, BeanStore ctx) {
-				got[0] = ctx;
-				return new McpPage<>(all, null);
-			}
-		};
-		c.page(java.util.List.of(), null, bs);
-		assertSame(bs, got[0]);
-	}
 }
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpRestServlet_Test.java b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpRestServlet20250618_Test.java
similarity index 93%
rename from juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpRestServlet_Test.java
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpRestServlet20250618_Test.java
index a129f1979e..53dd178d03 100644
--- a/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpRestServlet_Test.java
+++ b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpRestServlet20250618_Test.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.juneau.rest.server.mcp;
+package org.apache.juneau.rest.server.mcp.v20250618;
 
 import static org.apache.juneau.test.bct.BctAssertions.*;
 import static org.junit.jupiter.api.Assertions.*;
@@ -28,15 +28,16 @@ import org.apache.juneau.commons.inject.*;
 import org.apache.juneau.marshall.json.*;
 import org.apache.juneau.rest.mock.classic.*;
 import org.apache.juneau.rest.server.*;
+import org.apache.juneau.rest.server.mcp.*;
 import org.junit.jupiter.api.*;
 
 /**
- * End-to-end coverage for {@link McpRestServlet} via {@link MockRestClient}.
+ * End-to-end coverage for {@link McpRestServlet20250618} via {@link MockRestClient}.
  */
 @SuppressWarnings({
 	"resource" // Test helpers return Closeables; Eclipse JDT @Owning warning is by design.
 })
-class McpRestServlet_Test extends TestBase {
+class McpRestServlet20250618_Test extends TestBase {
 
 	private static final JsonParser PAR = JsonParser.create()
 		.typePropertyName(Content.class, "type")
@@ -44,7 +45,7 @@ class McpRestServlet_Test extends TestBase {
 		.build();
 
 	@Rest(serializers = JsonSerializer.class, parsers = JsonParser.class, defaultAccept = "application/json")
-	public static class A extends McpRestServlet {
+	public static class A extends McpRestServlet20250618 {
 		private static final long serialVersionUID = 1L;
 
 		@Override
@@ -115,7 +116,7 @@ class McpRestServlet_Test extends TestBase {
 
 	@Rest(path = "/api", serializers = JsonSerializer.class, parsers = JsonParser.class, defaultAccept = "application/json")
 	@org.apache.juneau.marshall.serializer.SerializerConfig(addBeanTypes = "true")
-	public static class B extends org.apache.juneau.rest.server.servlet.BasicRestServlet implements McpEndpoint {
+	public static class B extends org.apache.juneau.rest.server.servlet.BasicRestServlet implements McpEndpoint20250618 {
 		private static final long serialVersionUID = 1L;
 
 		@Override
@@ -146,7 +147,7 @@ class McpRestServlet_Test extends TestBase {
 	// -------- failure modes --------
 
 	@Rest(serializers = JsonSerializer.class, parsers = JsonParser.class, defaultAccept = "application/json")
-	public static class C extends McpRestServlet {
+	public static class C extends McpRestServlet20250618 {
 		private static final long serialVersionUID = 1L;
 		@Override
 		protected McpServerConfig createMcpConfig() {
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpServerConfig_Test.java b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpServerConfig_Test.java
similarity index 97%
rename from juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpServerConfig_Test.java
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpServerConfig_Test.java
index 57437172fc..6ec4f9fbd6 100644
--- a/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpServerConfig_Test.java
+++ b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpServerConfig_Test.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.juneau.rest.server.mcp;
+package org.apache.juneau.rest.server.mcp.v20250618;
 
 import static org.apache.juneau.test.bct.BctAssertions.*;
 import static org.junit.jupiter.api.Assertions.*;
@@ -23,6 +23,7 @@ import java.util.*;
 
 import org.apache.juneau.bean.mcp.v20250618.*;
 import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.rest.server.mcp.*;
 import org.junit.jupiter.api.*;
 
 /**
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/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
similarity index 90%
rename from juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpTypedHandlers_Test.java
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpTypedHandlers_Test.java
index 50f488eb59..7c5f2c2157 100644
--- a/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/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
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.juneau.rest.server.mcp;
+package org.apache.juneau.rest.server.mcp.v20250618;
 
 import static org.apache.juneau.test.bct.BctAssertions.*;
 import static org.junit.jupiter.api.Assertions.*;
@@ -25,6 +25,7 @@ import org.apache.juneau.bean.jsonrpc.*;
 import org.apache.juneau.bean.mcp.v20250618.*;
 import org.apache.juneau.commons.inject.*;
 import org.apache.juneau.marshall.collections.*;
+import org.apache.juneau.rest.server.mcp.*;
 import org.junit.jupiter.api.*;
 
 /**
@@ -50,7 +51,11 @@ class McpTypedHandlers_Test {
 	}
 
 	private final BeanStore ctx = new BasicBeanStore();
-	private final McpDispatcher dispatcher = new McpDispatcher();
+	private final Mcp20250618Revision revision = new Mcp20250618Revision(null);
+
+	private JsonRpcResponse dispatch(JsonRpcRequest req, McpServerConfig config) {
+		return revision.dispatch(new McpExchange(req, n -> null), config, ctx);
+	}
 
 	@Test
 	void a01_typedTool_argsBound_andResultWrappedAsText() {
@@ -78,7 +83,7 @@ class McpTypedHandlers_Test {
 			.setId(1)
 			.setMethod(McpMethods.TOOLS_CALL)
 			.setParams(JsonMap.of("name", "echo", "arguments", JsonMap.of("message", "hi", "repeat", 3)));
-		var resp = dispatcher.dispatch(req, config, ctx);
+		var resp = dispatch(req, config);
 		var ctr = (CallToolResult) resp.getResult();
 		var text = ((TextContent) ctr.getContent().get(0)).getText();
 		assertContains("\"text\":\"hi:3\"", text);
@@ -109,7 +114,7 @@ class McpTypedHandlers_Test {
 			.setId(1)
 			.setMethod(McpMethods.TOOLS_CALL)
 			.setParams(JsonMap.of("name", "d"));
-		var resp = dispatcher.dispatch(req, config, ctx);
+		var resp = dispatch(req, config);
 		assertSame(ctr, resp.getResult());
 	}
 
@@ -124,9 +129,9 @@ class McpTypedHandlers_Test {
 			public String call(EchoArgs args, BeanStore ctx) { return "hello"; }
 		};
 		var config = new McpServerConfig().addTool(McpTypedHandlers.adaptTool(typed));
-		var resp = dispatcher.dispatch(new JsonRpcRequest()
+		var resp = dispatch(new JsonRpcRequest()
 			.setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
-			.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "s")), config, ctx);
+			.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "s")), config);
 		var ctr = (CallToolResult) resp.getResult();
 		assertString("hello", ((TextContent) ctr.getContent().get(0)).getText());
 	}
@@ -142,9 +147,9 @@ class McpTypedHandlers_Test {
 			public EchoResult call(EchoArgs args, BeanStore ctx) { return null; }
 		};
 		var config = new McpServerConfig().addTool(McpTypedHandlers.adaptTool(typed));
-		var resp = dispatcher.dispatch(new JsonRpcRequest()
+		var resp = dispatch(new JsonRpcRequest()
 			.setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
-			.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "n")), config, ctx);
+			.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "n")), config);
 		var ctr = (CallToolResult) resp.getResult();
 		assertString("", ((TextContent) ctr.getContent().get(0)).getText());
 	}
@@ -162,9 +167,9 @@ class McpTypedHandlers_Test {
 			}
 		};
 		var config = new McpServerConfig().addTool(McpTypedHandlers.adaptTool(typed));
-		var resp = dispatcher.dispatch(new JsonRpcRequest()
+		var resp = dispatch(new JsonRpcRequest()
 			.setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
-			.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "z")), config, ctx);
+			.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "z")), config);
 		var ctr = (CallToolResult) resp.getResult();
 		assertString("null", ((TextContent) ctr.getContent().get(0)).getText());
 	}
@@ -181,11 +186,11 @@ class McpTypedHandlers_Test {
 		};
 		var config = new McpServerConfig().addTool(McpTypedHandlers.adaptTool(typed));
 		// Bad: 'repeat' should be int, supply a non-numeric value to trigger parser failure.
-		var resp = dispatcher.dispatch(new JsonRpcRequest()
+		var resp = dispatch(new JsonRpcRequest()
 			.setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
 			.setMethod(McpMethods.TOOLS_CALL)
-			.setParams(JsonMap.of("name", "x", "arguments", JsonMap.of("repeat", "not-an-int"))), config, ctx);
-		assertEquals(McpDispatcher.CODE_INVALID_PARAMS, resp.getError().getCode());
+			.setParams(JsonMap.of("name", "x", "arguments", JsonMap.of("repeat", "not-an-int"))), config);
+		assertEquals(Mcp20250618Revision.CODE_INVALID_PARAMS, resp.getError().getCode());
 	}
 
 	@Test
@@ -202,9 +207,9 @@ class McpTypedHandlers_Test {
 		};
 		var raw = McpTypedHandlers.adaptPrompt(typed);
 		var config = new McpServerConfig().addPrompt(raw);
-		var resp = dispatcher.dispatch(new JsonRpcRequest()
+		var resp = dispatch(new JsonRpcRequest()
 			.setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
-			.setMethod(McpMethods.PROMPTS_GET).setParams(JsonMap.of("name", "p")), config, ctx);
+			.setMethod(McpMethods.PROMPTS_GET).setParams(JsonMap.of("name", "p")), config);
 		var pr = (GetPromptResult) resp.getResult();
 		assertString("null", pr.getDescription());
 	}
@@ -260,10 +265,10 @@ class McpTypedHandlers_Test {
 			public Unserializable call(EchoArgs args, BeanStore ctx) { return new Unserializable(); }
 		};
 		var config = new McpServerConfig().addTool(McpTypedHandlers.adaptTool(typed));
-		var resp = dispatcher.dispatch(new JsonRpcRequest()
+		var resp = dispatch(new JsonRpcRequest()
 			.setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
-			.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "u")), config, ctx);
-		assertEquals(McpDispatcher.CODE_INTERNAL_ERROR, resp.getError().getCode());
+			.setMethod(McpMethods.TOOLS_CALL).setParams(JsonMap.of("name", "u")), config);
+		assertEquals(Mcp20250618Revision.CODE_INTERNAL_ERROR, resp.getError().getCode());
 	}
 
 	@Test
@@ -280,10 +285,10 @@ class McpTypedHandlers_Test {
 		};
 		var raw = McpTypedHandlers.adaptPrompt(typed);
 		var config = new McpServerConfig().addPrompt(raw);
-		var resp = dispatcher.dispatch(new JsonRpcRequest()
+		var resp = dispatch(new JsonRpcRequest()
 			.setJsonrpc(McpProtocol.JSON_RPC_2_0).setId(1)
 			.setMethod(McpMethods.PROMPTS_GET)
-			.setParams(JsonMap.of("name", "p", "arguments", JsonMap.of("message", "hello"))), config, ctx);
+			.setParams(JsonMap.of("name", "p", "arguments", JsonMap.of("message", "hello"))), config);
 		var pr = (GetPromptResult) resp.getResult();
 		assertString("hello", pr.getDescription());
 	}
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/CAPS-initialize.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/CAPS-initialize.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/CAPS-initialize.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/CAPS-initialize.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/CAPS-initialize.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/CAPS-initialize.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/CAPS-initialize.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/CAPS-initialize.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-empty-method.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-empty-method.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-empty-method.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-empty-method.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-empty-method.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-empty-method.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-empty-method.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-empty-method.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-initialize.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-initialize.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-initialize.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-initialize.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-initialize.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-initialize.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-initialize.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-initialize.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-missing-method.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-missing-method.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-missing-method.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-missing-method.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-missing-method.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-missing-method.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-missing-method.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-missing-method.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-notification-ping.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-notification-ping.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-notification-ping.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-notification-ping.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-notification-ping.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-notification-ping.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-notification-ping.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-notification-ping.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-notification-unknown.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-notification-unknown.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-notification-unknown.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-notification-unknown.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-notification-unknown.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-notification-unknown.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-notification-unknown.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-notification-unknown.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-ping.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-ping.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-ping.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-ping.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-ping.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-ping.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-ping.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-ping.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-prompts-get-missing-name.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-prompts-get-missing-name.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-prompts-get-missing-name.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-prompts-get-missing-name.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-prompts-get-missing-name.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-prompts-get-missing-name.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-prompts-get-missing-name.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-prompts-get-missing-name.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-prompts-get-unknown.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-prompts-get-unknown.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-prompts-get-unknown.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-prompts-get-unknown.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-prompts-get-unknown.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-prompts-get-unknown.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-prompts-get-unknown.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-prompts-get-unknown.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-prompts-list.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-prompts-list.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-prompts-list.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-prompts-list.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-prompts-list.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-prompts-list.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-prompts-list.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-prompts-list.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-resources-list.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-resources-list.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-resources-list.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-resources-list.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-resources-list.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-resources-list.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-resources-list.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-resources-list.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-resources-read-missing-uri.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-resources-read-missing-uri.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-resources-read-missing-uri.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-resources-read-missing-uri.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-resources-read-missing-uri.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-resources-read-missing-uri.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-resources-read-missing-uri.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-resources-read-missing-uri.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-resources-read-unknown.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-resources-read-unknown.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-resources-read-unknown.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-resources-read-unknown.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-resources-read-unknown.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-resources-read-unknown.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-resources-read-unknown.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-resources-read-unknown.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-tools-call-missing-name.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-tools-call-missing-name.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-tools-call-missing-name.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-tools-call-missing-name.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-tools-call-missing-name.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-tools-call-missing-name.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-tools-call-missing-name.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-tools-call-missing-name.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-tools-call-unknown-tool.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-tools-call-unknown-tool.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-tools-call-unknown-tool.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-tools-call-unknown-tool.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-tools-call-unknown-tool.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-tools-call-unknown-tool.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-tools-call-unknown-tool.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-tools-call-unknown-tool.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-tools-list.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-tools-list.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-tools-list.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-tools-list.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-tools-list.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-tools-list.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-tools-list.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-tools-list.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-unknown-method.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-unknown-method.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-unknown-method.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-unknown-method.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-unknown-method.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-unknown-method.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/EMPTY-unknown-method.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/EMPTY-unknown-method.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-initialize.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-initialize.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-initialize.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-initialize.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-initialize.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-initialize.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-initialize.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-initialize.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-prompts-get.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-prompts-get.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-prompts-get.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-prompts-get.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-prompts-get.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-prompts-get.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-prompts-get.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-prompts-get.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-prompts-list.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-prompts-list.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-prompts-list.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-prompts-list.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-prompts-list.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-prompts-list.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-prompts-list.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-prompts-list.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-resources-list.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-resources-list.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-resources-list.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-resources-list.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-resources-list.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-resources-list.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-resources-list.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-resources-list.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-resources-read.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-resources-read.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-resources-read.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-resources-read.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-resources-read.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-resources-read.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-resources-read.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-resources-read.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-string-id.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-string-id.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-string-id.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-string-id.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-string-id.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-string-id.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-string-id.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-string-id.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-arguments-not-map.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-arguments-not-map.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-arguments-not-map.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-arguments-not-map.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-arguments-not-map.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-arguments-not-map.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-arguments-not-map.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-arguments-not-map.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-error-flag.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-error-flag.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-error-flag.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-error-flag.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-error-flag.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-error-flag.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-error-flag.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-error-flag.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-mixed-content.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-mixed-content.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-mixed-content.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-mixed-content.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-mixed-content.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-mixed-content.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-mixed-content.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-mixed-content.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-no-arguments.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-no-arguments.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-no-arguments.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-no-arguments.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-no-arguments.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-no-arguments.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-no-arguments.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-no-arguments.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-params-not-map.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-params-not-map.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-params-not-map.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-params-not-map.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-params-not-map.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-params-not-map.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-params-not-map.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-params-not-map.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-text.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-text.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-text.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-text.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-text.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-text.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-call-text.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-call-text.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-list.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-list.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-list.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-list.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-list.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-list.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/FULL-tools-list.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/FULL-tools-list.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/PAGED-tools-list-first.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/PAGED-tools-list-first.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/PAGED-tools-list-first.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/PAGED-tools-list-first.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/PAGED-tools-list-first.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/PAGED-tools-list-first.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/PAGED-tools-list-first.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/PAGED-tools-list-first.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/PAGED-tools-list-second.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/PAGED-tools-list-second.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/PAGED-tools-list-second.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/PAGED-tools-list-second.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/PAGED-tools-list-second.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/PAGED-tools-list-second.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/PAGED-tools-list-second.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/PAGED-tools-list-second.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/THROW-mcp-exception.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/THROW-mcp-exception.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/THROW-mcp-exception.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/THROW-mcp-exception.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/THROW-mcp-exception.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/THROW-mcp-exception.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/THROW-mcp-exception.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/THROW-mcp-exception.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/THROW-no-message.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/THROW-no-message.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/THROW-no-message.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/THROW-no-message.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/THROW-no-message.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/THROW-no-message.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/THROW-no-message.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/THROW-no-message.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/THROW-runtime-exception.request.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/THROW-runtime-exception.request.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/THROW-runtime-exception.request.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/THROW-runtime-exception.request.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/THROW-runtime-exception.response.json b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/THROW-runtime-exception.response.json
similarity index 100%
rename from juneau-rest/juneau-rest-server-mcp/src/test/resources/characterization/THROW-runtime-exception.response.json
rename to juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/resources/characterization/THROW-runtime-exception.response.json
diff --git a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/Mcp.java b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/Mcp.java
deleted file mode 100644
index 9c388cd109..0000000000
--- a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/Mcp.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.bean.jsonrpc.*;
-import org.apache.juneau.bean.mcp.v20250618.*;
-import org.apache.juneau.commons.inject.*;
-
-/**
- * Static façade over {@link McpDispatcher} for embedders that want a single-line dispatch call.
- *
- * <p>
- * Use this from inside a custom Juneau REST resource that wants to expose an MCP endpoint without
- * subclassing {@link McpRestServlet}:
- *
- * <pre>
- * @Rest(path="/api")
- * public class MyResource extends BasicRestServlet {
- *     @RestPost(path="/mcp")
- *     public JsonRpcResponse mcp(JsonRpcRequest req, RestRequest rr) {
- *         return Mcp.handle(req, getMcpConfig(), rr.getContext().getBeanStore());
- *     }
- *
- *     private McpServerConfig getMcpConfig() { ... }
- * }
- * </pre>
- */
-public final class Mcp {
-
-	private static final McpDispatcher DISPATCHER = new McpDispatcher();
-
-	private Mcp() {}
-
-	/**
-	 * Dispatches a JSON-RPC request through the shared {@link McpDispatcher} instance.
-	 *
-	 * @param req JSON-RPC request envelope. May be {@code null} (treated as an invalid request).
-	 * @param config Server config. Never {@code null}.
-	 * @param ctx Per-request bean store. Never {@code null}.
-	 * @return The response, or {@code null} for notification requests.
-	 */
-	public static JsonRpcResponse handle(JsonRpcRequest req, McpServerConfig config, BeanStore ctx) {
-		return DISPATCHER.dispatch(req, config, ctx);
-	}
-}
diff --git a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpDispatcher.java b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpDispatcher.java
deleted file mode 100644
index 20c1a0c363..0000000000
--- a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpDispatcher.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * 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.apache.juneau.commons.utils.AssertionUtils.*;
-import static org.apache.juneau.commons.utils.Shorts.*;
-import static org.apache.juneau.commons.utils.StringUtils.*;
-
-import java.util.*;
-
-import org.apache.juneau.bean.jsonrpc.*;
-import org.apache.juneau.bean.mcp.v20250618.*;
-import org.apache.juneau.commons.inject.*;
-import org.apache.juneau.marshall.collections.*;
-
-/**
- * Transport-agnostic JSON-RPC dispatcher for the MCP wire protocol.
- *
- * <p>
- * The dispatcher contains <em>zero</em> HTTP plumbing; it accepts a parsed {@link JsonRpcRequest},
- * routes by {@code method}, and returns a {@link JsonRpcResponse}. {@link McpRestServlet} is a thin
- * adapter that wraps this dispatcher in a Juneau REST {@code @RestPost} method, but embedders are
- * free to call {@link #dispatch(JsonRpcRequest, McpServerConfig, BeanStore)} directly from
- * tests or from a non-Juneau servlet.
- *
- * <p>
- * Notification requests (those with a {@code null} {@link JsonRpcRequest#getId() id}) return
- * {@code null}; HTTP adapters should map this to {@code 204 No Content}.
- */
-public class McpDispatcher {
-
-	/** JSON-RPC error code: parse error. */
-	public static final int CODE_PARSE_ERROR = -32700;
-
-	/** JSON-RPC error code: invalid request. */
-	public static final int CODE_INVALID_REQUEST = -32600;
-
-	/** JSON-RPC error code: method not found. */
-	public static final int CODE_METHOD_NOT_FOUND = -32601;
-
-	/** JSON-RPC error code: invalid params. */
-	public static final int CODE_INVALID_PARAMS = -32602;
-
-	/** JSON-RPC error code: internal error. */
-	public static final int CODE_INTERNAL_ERROR = -32603;
-
-	/**
-	 * Default server name used when {@link McpServerConfig#getServerInfo()} is {@code null}.
-	 */
-	public static final String DEFAULT_SERVER_NAME = "juneau-rest-server-mcp";
-
-	/**
-	 * Dispatch the supplied JSON-RPC request against the supplied configuration.
-	 *
-	 * @param req JSON-RPC request envelope. Never {@code null}.
-	 * @param config Server config (handler registry, capabilities, cursor strategy). Never {@code null}.
-	 * @param ctx Per-request bean store (passed through to handlers). Never {@code null}.
-	 * @return The response, or {@code null} for notification requests.
-	 */
-	public JsonRpcResponse dispatch(JsonRpcRequest req, McpServerConfig config, BeanStore ctx) {
-		assertArgNotNull("config", config);
-		assertArgNotNull("ctx", ctx);
-		if (req == null)
-			return errorResponse(null, CODE_INVALID_REQUEST, "Request envelope is null", null);
-
-		var id = req.getId();
-		var method = req.getMethod();
-
-		if (isEmpty(method))
-			return notification(id) ? null : errorResponse(id, CODE_INVALID_REQUEST, "Missing method", null);
-
-		try {
-			var result = invoke(method, req.getParams(), config, ctx);
-			return notification(id) ? null : ok(id, result);
-		} catch (McpException e) {
-			return notification(id) ? null : new JsonRpcResponse()
-				.setJsonrpc(McpProtocol.JSON_RPC_2_0)
-				.setId(id)
-				.setError(e.toJsonRpcError());
-		} catch (Exception e) {
-			if (notification(id))
-				return null;
-			var message = e.getMessage() == null ? cns(e) : e.getMessage();
-			return errorResponse(id, CODE_INTERNAL_ERROR, message, JsonMap.of("type", cn(e)));
-		}
-	}
-
-	private static boolean notification(Object id) {
-		return id == null;
-	}
-
-	private Object invoke(String method, Object params, McpServerConfig config, BeanStore ctx) {
-		switch (method) {
-			case McpMethods.INITIALIZE:
-				return initialize(config);
-			case McpMethods.PING:
-				return new JsonMap();
-			case McpMethods.TOOLS_LIST:
-				return listTools(config, params, ctx);
-			case McpMethods.TOOLS_CALL:
-				return callTool(config, params, ctx);
-			case McpMethods.PROMPTS_LIST:
-				return listPrompts(config, params, ctx);
-			case McpMethods.PROMPTS_GET:
-				return getPrompt(config, params, ctx);
-			case McpMethods.RESOURCES_LIST:
-				return listResources(config, params, ctx);
-			case McpMethods.RESOURCES_READ:
-				return readResource(config, params, ctx);
-			default:
-				throw new McpException(CODE_METHOD_NOT_FOUND, "Method not found: " + method);
-		}
-	}
-
-	// -------------------------------------------------------------------------------------------
-	// initialize / capabilities
-	// -------------------------------------------------------------------------------------------
-
-	private static InitializeResult initialize(McpServerConfig config) {
-		var caps = config.getCapabilities();
-		if (caps == null) {
-			caps = new ServerCapabilities();
-			if (! config.getTools().isEmpty())
-				caps.setTools(new ToolCapability());
-			if (! config.getPrompts().isEmpty())
-				caps.setPrompts(new PromptCapability());
-			if (! config.getResources().isEmpty())
-				caps.setResources(new ResourceCapability());
-		}
-		var info = config.getServerInfo();
-		if (info == null)
-			info = new Implementation().setName(DEFAULT_SERVER_NAME).setVersion("unknown");
-		return new InitializeResult()
-			.setProtocolVersion(config.getProtocolVersion())
-			.setCapabilities(caps)
-			.setServerInfo(info)
-			.setInstructions(config.getInstructions());
-	}
-
-	// -------------------------------------------------------------------------------------------
-	// tools
-	// -------------------------------------------------------------------------------------------
-
-	private static ListToolsResult listTools(McpServerConfig config, Object params, BeanStore ctx) {
-		var descriptors = config.getTools().stream().map(McpToolHandler::descriptor).toList();
-		var page = config.getCursor().page(descriptors, cursorOf(params), ctx);
-		return new ListToolsResult().setTools(page.items()).setNextCursor(page.nextCursor());
-	}
-
-	private static CallToolResult callTool(McpServerConfig config, Object params, BeanStore ctx) {
-		var p = asMap(params);
-		var name = strParam(p, "name");
-		if (name == null)
-			throw new McpException(CODE_INVALID_PARAMS, "Missing tool name");
-		var handler = config.getTools().stream()
-			.filter(h -> name.equals(h.descriptor().getName()))
-			.findFirst()
-			.orElseThrow(() -> new McpException(CODE_METHOD_NOT_FOUND, "Tool not found: " + name));
-		var args = mapParam(p, "arguments");
-		return handler.call(args, ctx);
-	}
-
-	// -------------------------------------------------------------------------------------------
-	// prompts
-	// -------------------------------------------------------------------------------------------
-
-	private static ListPromptsResult listPrompts(McpServerConfig config, Object params, BeanStore ctx) {
-		var descriptors = config.getPrompts().stream().map(McpPromptHandler::descriptor).toList();
-		var page = config.getCursor().page(descriptors, cursorOf(params), ctx);
-		return new ListPromptsResult().setPrompts(page.items()).setNextCursor(page.nextCursor());
-	}
-
-	private static GetPromptResult getPrompt(McpServerConfig config, Object params, BeanStore ctx) {
-		var p = asMap(params);
-		var name = strParam(p, "name");
-		if (name == null)
-			throw new McpException(CODE_INVALID_PARAMS, "Missing prompt name");
-		var handler = config.getPrompts().stream()
-			.filter(h -> name.equals(h.descriptor().getName()))
-			.findFirst()
-			.orElseThrow(() -> new McpException(CODE_METHOD_NOT_FOUND, "Prompt not found: " + name));
-		var args = mapParam(p, "arguments");
-		return handler.get(args, ctx);
-	}
-
-	// -------------------------------------------------------------------------------------------
-	// resources
-	// -------------------------------------------------------------------------------------------
-
-	private static ListResourcesResult listResources(McpServerConfig config, Object params, BeanStore ctx) {
-		var descriptors = config.getResources().stream().map(McpResourceHandler::descriptor).toList();
-		var page = config.getCursor().page(descriptors, cursorOf(params), ctx);
-		return new ListResourcesResult().setResources(page.items()).setNextCursor(page.nextCursor());
-	}
-
-	private static ReadResourceResult readResource(McpServerConfig config, Object params, BeanStore ctx) {
-		var p = asMap(params);
-		var uri = strParam(p, "uri");
-		if (uri == null)
-			throw new McpException(CODE_INVALID_PARAMS, "Missing resource uri");
-		var handler = config.getResources().stream()
-			.filter(h -> uri.equals(h.descriptor().getUri()))
-			.findFirst()
-			.orElseThrow(() -> new McpException(CODE_METHOD_NOT_FOUND, "Resource not found: " + uri));
-		return handler.read(uri, ctx);
-	}
-
-	// -------------------------------------------------------------------------------------------
-	// helpers
-	// -------------------------------------------------------------------------------------------
-
-	private static String cursorOf(Object params) {
-		return strParam(asMap(params), "cursor");
-	}
-
-	@SuppressWarnings({
-		"unchecked" // Cast is safe: type parameter verified by MCP protocol contract.
-	})
-	private static Map<String,Object> asMap(Object params) {
-		if (params == null)
-			return Map.of();
-		if (params instanceof Map)
-			return (Map<String,Object>) params;
-		throw new McpException(CODE_INVALID_PARAMS, "Params must be an object");
-	}
-
-	private static String strParam(Map<String,Object> p, String key) {
-		var v = p.get(key);
-		return v == null ? null : v.toString();
-	}
-
-	@SuppressWarnings({
-		"unchecked" // Cast is safe: type parameter verified by MCP protocol contract.
-	})
-	private static Map<String,Object> mapParam(Map<String,Object> p, String key) {
-		var v = p.get(key);
-		if (v == null)
-			return Map.of();
-		if (v instanceof Map)
-			return (Map<String,Object>) v;
-		throw new McpException(CODE_INVALID_PARAMS, "Param '" + key + "' must be an object");
-	}
-
-	private static JsonRpcResponse ok(Object id, Object result) {
-		return new JsonRpcResponse()
-			.setJsonrpc(McpProtocol.JSON_RPC_2_0)
-			.setId(id)
-			.setResult(result);
-	}
-
-	private static JsonRpcResponse errorResponse(Object id, int code, String message, Object data) {
-		return new JsonRpcResponse()
-			.setJsonrpc(McpProtocol.JSON_RPC_2_0)
-			.setId(id)
-			.setError(new JsonRpcError().setCode(code).setMessage(message).setData(data));
-	}
-}
diff --git a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpEndpoint.java b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpEndpoint.java
index 2302bf5fd3..1df1bd99a6 100644
--- a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpEndpoint.java
+++ b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpEndpoint.java
@@ -17,7 +17,6 @@
 package org.apache.juneau.rest.server.mcp;
 
 import org.apache.juneau.bean.jsonrpc.*;
-import org.apache.juneau.bean.mcp.v20250618.*;
 import org.apache.juneau.commons.inject.*;
 import org.apache.juneau.http.Content;
 import org.apache.juneau.rest.server.*;
@@ -26,14 +25,14 @@ import org.apache.juneau.rest.server.*;
  * Mixin interface that exposes an MCP JSON-RPC endpoint at {@code POST /mcp} on any Juneau REST resource.
  *
  * <p>
- * Implementing classes provide their {@link McpServerConfig} by implementing {@link #getMcpConfig()}; the
- * default {@link #handleMcpRequest(JsonRpcRequest, RestRequest)} method dispatches incoming requests through
- * {@link Mcp#handle(JsonRpcRequest, McpServerConfig, BeanStore)}.
+ * Implementing classes supply their {@link McpServerConfig} via {@link #getMcpConfig()} and their
+ * protocol revision via {@link #revision()}. In practice a consumer implements a revision-specific
+ * sub-interface (for example {@code McpEndpoint20250618}), which supplies {@link #revision()} for them.
  *
  * <h5 class='section'>Example:</h5>
  * <pre>
  * @Rest(path="/api")
- * public class MyResource extends BasicRestServlet implements McpEndpoint {
+ * public class MyResource extends BasicRestServlet implements McpEndpoint20250618 {
  *     @Override
  *     public McpServerConfig getMcpConfig() {
  *         return new McpServerConfig().addTool(new MyEchoTool());
@@ -50,12 +49,19 @@ public interface McpEndpoint {
 	 */
 	McpServerConfig getMcpConfig();
 
+	/**
+	 * The MCP protocol revision this endpoint speaks.
+	 *
+	 * @return The bound revision. Never {@code null}.
+	 */
+	McpRevision revision();
+
 	/**
 	 * Default MCP JSON-RPC endpoint handler.
 	 *
 	 * <p>
-	 * Implementations may override this method to customize routing (path / annotations) but must still
-	 * call {@link Mcp#handle(JsonRpcRequest, McpServerConfig, BeanStore)} to dispatch.
+	 * Implementations may override this method to customize routing (path / annotations) but must
+	 * still dispatch through {@link #revision()}.
 	 *
 	 * @param req JSON-RPC request envelope.
 	 * @param restReq The current REST request.
@@ -68,6 +74,7 @@ public interface McpEndpoint {
 	default JsonRpcResponse handleMcpRequest(@Content JsonRpcRequest req, RestRequest restReq) {
 		var bs = new BasicBeanStore(restReq.getContext().getBeanStore())
 			.addBean(RestRequest.class, restReq);
-		return Mcp.handle(req, getMcpConfig(), bs);
+		var exchange = new McpExchange(req, n -> restReq.getHeaderParam(n).asString().orElse(null));
+		return revision().dispatch(exchange, getMcpConfig(), bs);
 	}
 }
diff --git a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpPromptHandler.java b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpPromptHandler.java
index 9b91d6a049..e8612f8d8a 100644
--- a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpPromptHandler.java
+++ b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpPromptHandler.java
@@ -35,8 +35,9 @@ public interface McpPromptHandler {
 	 * Returns the static descriptor for this prompt.
 	 *
 	 * <p>
-	 * The {@link Prompt#getName() name} value is used by {@link McpDispatcher} to route incoming
-	 * {@code prompts/get} requests, so each handler in an {@link McpServerConfig} must use a unique name.
+	 * The {@link Prompt#getName() name} value is used by the bound {@link McpRevision} to route
+	 * incoming {@code prompts/get} requests, so each handler in an {@link McpServerConfig} must use a
+	 * unique name.
 	 *
 	 * @return The prompt descriptor. Never {@code null}.
 	 */
diff --git a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpResourceHandler.java b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpResourceHandler.java
index 08eed9aeaa..db02fe3e77 100644
--- a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpResourceHandler.java
+++ b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpResourceHandler.java
@@ -33,8 +33,9 @@ public interface McpResourceHandler {
 	 * Returns the static descriptor for this resource.
 	 *
 	 * <p>
-	 * The {@link Resource#getUri() uri} value is used by {@link McpDispatcher} to route incoming
-	 * {@code resources/read} requests, so each handler in an {@link McpServerConfig} must use a unique URI.
+	 * The {@link Resource#getUri() uri} value is used by the bound {@link McpRevision} to route
+	 * incoming {@code resources/read} requests, so each handler in an {@link McpServerConfig} must use
+	 * a unique URI.
 	 *
 	 * @return The resource descriptor. Never {@code null}.
 	 */
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 8414192cd4..3475544b92 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
@@ -37,7 +37,7 @@ import org.apache.juneau.rest.server.servlet.*;
  * <h5 class='section'>Example:</h5>
  * <pre>
  * @Rest(path="/mcp")
- * public class MyMcpServlet extends McpRestServlet {
+ * public class MyMcpServlet extends McpRestServlet20250618 {
  *     @Override
  *     protected McpServerConfig createMcpConfig() {
  *         return new McpServerConfig()
@@ -48,8 +48,9 @@ import org.apache.juneau.rest.server.servlet.*;
  * </pre>
  *
  * <p>
- * The servlet enables {@code addBeanTypes} on its serializer so {@link Content} and {@link ResourceContents}
- * polymorphic types are tagged with their {@code type} discriminator on the wire.
+ * The servlet enables {@code addBeanTypes} on its serializer so that polymorphic wire types a
+ * revision emits (content blocks, resource payloads) are tagged with their {@code type}
+ * discriminator.
  *
  * @serial exclude
  */
@@ -98,6 +99,17 @@ public abstract class McpRestServlet extends BasicRestServlet {
 	 */
 	protected abstract McpServerConfig createMcpConfig();
 
+	/**
+	 * The MCP protocol revision this servlet speaks.
+	 *
+	 * <p>
+	 * Supplied by the revision-specific subclass a consumer extends (for example
+	 * {@code McpRestServlet20250618}); consumers do not normally implement this themselves.
+	 *
+	 * @return The bound revision. Never {@code null}.
+	 */
+	protected abstract McpRevision revision();
+
 	/**
 	 * MCP JSON-RPC endpoint.
 	 *
@@ -113,6 +125,7 @@ public abstract class McpRestServlet extends BasicRestServlet {
 	public JsonRpcResponse handleMcp(@Content JsonRpcRequest req, RestRequest restReq) {
 		var bs = new BasicBeanStore(restReq.getContext().getBeanStore())
 			.addBean(RestRequest.class, restReq);
-		return Mcp.handle(req, getMcpConfig(), bs);
+		var exchange = new McpExchange(req, n -> restReq.getHeaderParam(n).asString().orElse(null));
+		return revision().dispatch(exchange, getMcpConfig(), bs);
 	}
 }
diff --git a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpServerConfig.java b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpServerConfig.java
index 8ddae0d0ad..da8fd2ac99 100644
--- a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpServerConfig.java
+++ b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpServerConfig.java
@@ -23,7 +23,7 @@ import java.util.*;
 import org.apache.juneau.bean.mcp.v20250618.*;
 
 /**
- * Aggregate configuration consumed by {@link McpDispatcher}.
+ * Aggregate configuration consumed by the bound {@link McpRevision}.
  *
  * <p>
  * Applications register a single {@link McpServerConfig} (typically as a bean in their {@code RestContext}
@@ -45,7 +45,7 @@ public class McpServerConfig {
 	 * Server identity reported in {@code initialize}.
 	 *
 	 * <p>
-	 * If {@code null}, {@link McpDispatcher} fills in {@code "juneau-rest-server-mcp" / "unknown"}.
+	 * If {@code null}, the bound {@link McpRevision} fills in a default identity.
 	 *
 	 * @return The server info, or {@code null} if not set.
 	 */
@@ -201,7 +201,7 @@ public class McpServerConfig {
 	 * Optional explicit capabilities advertisement.
 	 *
 	 * <p>
-	 * When {@code null}, {@link McpDispatcher} synthesizes one from the registered handler lists.
+	 * When {@code null}, the bound {@link McpRevision} synthesizes one from the registered handler lists.
 	 *
 	 * @return The override, or {@code null} if auto-derived.
 	 */
diff --git a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpToolHandler.java b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpToolHandler.java
index 3d547976ff..6d0169d020 100644
--- a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpToolHandler.java
+++ b/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpToolHandler.java
@@ -39,8 +39,9 @@ public interface McpToolHandler {
 	 * Returns the static descriptor for this tool.
 	 *
 	 * <p>
-	 * The {@link Tool#getName() name} value is used by {@link McpDispatcher} to route incoming
-	 * {@code tools/call} requests, so each handler in an {@link McpServerConfig} must use a unique name.
+	 * The {@link Tool#getName() name} value is used by the bound {@link McpRevision} to route
+	 * incoming {@code tools/call} requests, so each handler in an {@link McpServerConfig} must use a
+	 * unique name.
 	 *
 	 * @return The tool descriptor. Never {@code null}.
 	 */
diff --git a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpTypedPromptHandler.java b/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpCursorCtx_Test.java
similarity index 53%
rename from juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpTypedPromptHandler.java
rename to juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpCursorCtx_Test.java
index 524737d38a..8df4f8ca2d 100644
--- a/juneau-rest/juneau-rest-server-mcp/src/main/java/org/apache/juneau/rest/server/mcp/McpTypedPromptHandler.java
+++ b/juneau-rest/juneau-rest-server-mcp/src/test/java/org/apache/juneau/rest/server/mcp/McpCursorCtx_Test.java
@@ -16,36 +16,29 @@
  */
 package org.apache.juneau.rest.server.mcp;
 
-import org.apache.juneau.bean.mcp.v20250618.*;
+import static org.junit.jupiter.api.Assertions.*;
+
 import org.apache.juneau.commons.inject.*;
+import org.junit.jupiter.api.*;
 
 /**
- * Typed variant of {@link McpPromptHandler} where MCP {@code prompts/get} arguments bind into a Juneau bean.
- *
- * @param <A> Argument bean type.
+ * Coverage for {@link McpCursor} bean-store passthrough.
  */
-public interface McpTypedPromptHandler<A> {
-
-	/**
-	 * Returns the static descriptor for this prompt.
-	 *
-	 * @return The prompt descriptor. Never {@code null}.
-	 */
-	Prompt descriptor();
-
-	/**
-	 * Returns the runtime argument class for binding.
-	 *
-	 * @return The argument class. Never {@code null}.
-	 */
-	Class<A> argumentType();
+class McpCursorCtx_Test {
 
-	/**
-	 * Renders the prompt.
-	 *
-	 * @param arguments Bound argument bean (may be {@code null} when no arguments are supplied).
-	 * @param ctx Per-request bean store.
-	 * @return The rendered prompt.
-	 */
-	GetPromptResult get(A arguments, BeanStore ctx);
+	@Test
+	void d01_cursor_passes_ctx_to_strategy() {
+		// Verify ctx parameter reaches the cursor (covers the BeanStore parameter passthrough).
+		var bs = new BasicBeanStore();
+		var got = new Object[1];
+		McpCursor c = new McpCursor() {
+			@Override
+			public <T> McpPage<T> page(java.util.List<T> all, String cursor, BeanStore ctx) {
+				got[0] = ctx;
+				return new McpPage<>(all, null);
+			}
+		};
+		c.page(java.util.List.of(), null, bs);
+		assertSame(bs, got[0]);
+	}
 }