diff --git a/pom.xml b/pom.xml
index f8093ca942..f4559e379d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -86,7 +86,7 @@
io.quarkiverse.mcp
quarkus-mcp-server-bom
- 1.13.0
+ 2.0.3
pom
import
@@ -485,6 +485,7 @@
+ ${project.version}
${argLine}
org.jboss.logmanager.LogManager
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpHttpTestSupport.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpHttpTestSupport.java
new file mode 100644
index 0000000000..737b565a0c
--- /dev/null
+++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpHttpTestSupport.java
@@ -0,0 +1,59 @@
+package io.stargate.sgv2.jsonapi.api.v1.mcp;
+
+import static io.restassured.RestAssured.given;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.restassured.http.ContentType;
+import io.restassured.response.Response;
+import io.restassured.specification.RequestSpecification;
+import io.vertx.core.json.JsonObject;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Shared wire format for raw HTTP tests that do not initialize an MCP client session. */
+final class McpHttpTestSupport {
+ private static final String CURRENT_PROTOCOL_VERSION = "2026-07-28";
+
+ private McpHttpTestSupport() {}
+
+ static RequestSpecification request(String method) {
+ return given()
+ .contentType(ContentType.JSON)
+ .accept("application/json, text/event-stream")
+ .header("MCP-Protocol-Version", CURRENT_PROTOCOL_VERSION)
+ .header("Mcp-Method", method);
+ }
+
+ static Map toolPayload(int id, String tool, Map arguments) {
+ return rpcPayload(id, "tools/call", Map.of("name", tool, "arguments", arguments));
+ }
+
+ static Map rpcPayload(int id, String method, Map params) {
+ var requestParams = new HashMap<>(params);
+ requestParams.put(
+ "_meta",
+ Map.of(
+ "io.modelcontextprotocol/protocolVersion",
+ CURRENT_PROTOCOL_VERSION,
+ "io.modelcontextprotocol/clientCapabilities",
+ Map.of()));
+ return Map.of("jsonrpc", "2.0", "id", id, "method", method, "params", requestParams);
+ }
+
+ static JsonObject assertResponseComplete(Response response, int id) {
+ assertThat(response.statusCode()).as(response.asString()).isEqualTo(200);
+ var body = new JsonObject(response.asString());
+ assertThat(body.getInteger("id")).isEqualTo(id);
+ assertThat(body.containsKey("error")).isFalse();
+ var result = body.getJsonObject("result");
+ assertThat(result.getString("resultType")).isEqualTo("complete");
+ return result;
+ }
+
+ static JsonObject assertResultReadable(JsonObject result) {
+ var content = result.getJsonArray("content");
+ assertThat(content).hasSize(1);
+ assertThat(content.getJsonObject(0).getString("type")).isEqualTo("text");
+ return new JsonObject(content.getJsonObject(0).getString("text"));
+ }
+}
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpIntegrationTestBase.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpIntegrationTestBase.java
index 392bf69c8f..03a16ae209 100644
--- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpIntegrationTestBase.java
+++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpIntegrationTestBase.java
@@ -176,16 +176,25 @@ protected Consumer assertStatusOnlyWithJson(Consumer s
};
}
+ private static void assertServerInfoOnly(ToolResponse response) {
+ assertThat(response._meta()).containsOnlyKeys(MetaKey.SERVER_INFO);
+ var serverInfo = (JsonObject) response._meta().get(MetaKey.SERVER_INFO);
+ assertThat(serverInfo.getString("name")).isEqualTo("Stargate DATA API");
+ assertThat(serverInfo.getString("version"))
+ .isEqualTo(
+ ConfigProvider.getConfig().getValue("quarkus.application.version", String.class));
+ }
+
/**
- * Assert error response structure is valid (no meta_ and content), then apply additional
- * assertions on the error JsonArray extracted from structuredContent.
+ * Assert that an error response has server metadata and an empty content field. Then apply
+ * additional assertions to the error JsonArray extracted from structuredContent.
*
* @param errorsAssertions additional assertions to run on the error JsonArray
*/
protected Consumer assertErrorOnly(Consumer errorsAssertions) {
return response -> {
assertThat(response.content()).isEmpty();
- assertThat(response._meta()).isEmpty();
+ assertServerInfoOnly(response);
assertTrue(response.isError());
assertThat(response.structuredContent()).isNotNull();
@@ -198,8 +207,8 @@ protected Consumer assertErrorOnly(Consumer errorsAsser
}
/**
- * Assert data only response is valid (no meta_, content and error), then apply additional
- * assertions on the structuredContent holds the data.
+ * Assert that a data response has server metadata, an empty content field, and no error. Then
+ * apply additional assertions to the data held in structuredContent.
*
* @param dataAssertions additional assertions to run on the data JsonObject
*/
@@ -207,7 +216,7 @@ protected Consumer assertDataOnly(Consumer dataAsserti
return response -> {
assertFalse(response.isError());
assertThat(response.content()).isEmpty();
- assertThat(response._meta()).isEmpty();
+ assertServerInfoOnly(response);
assertNotNull(response.structuredContent());
var data = (JsonObject) response.structuredContent();
diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpProtocolTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpProtocolTest.java
new file mode 100644
index 0000000000..75a989ad23
--- /dev/null
+++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpProtocolTest.java
@@ -0,0 +1,34 @@
+package io.stargate.sgv2.jsonapi.api.v1.mcp;
+
+import static io.stargate.sgv2.jsonapi.api.v1.mcp.McpHttpTestSupport.assertResponseComplete;
+import static io.stargate.sgv2.jsonapi.api.v1.mcp.McpHttpTestSupport.request;
+import static io.stargate.sgv2.jsonapi.api.v1.mcp.McpHttpTestSupport.rpcPayload;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.TestProfile;
+import io.stargate.sgv2.jsonapi.testresource.NoGlobalResourcesTestProfile;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+/** Exercises the HTTP wire contract without a client that might initialize a session for us. */
+@QuarkusTest
+@TestProfile(NoGlobalResourcesTestProfile.Impl.class)
+class McpProtocolTest {
+ @Test
+ void listsToolsWithoutInitializationOrSession() {
+ // Each POST is independent. clientInfo, discovery and initialization are deliberately absent.
+ for (int id = 1; id <= 2; id++) {
+ var response =
+ request("tools/list")
+ // Header auth checks only that a token is present. Database requests validate it,
+ // but tools/list returns metadata without accessing the database.
+ .header("Token", "protocol-test-credential")
+ .body(rpcPayload(id, "tools/list", Map.of()))
+ .post("/v1/mcp");
+ assertResponseComplete(response, id);
+ assertThat(response.header("Mcp-Session-Id")).isNull();
+ assertThat(response.jsonPath().getList("result.tools.name")).isNotEmpty();
+ }
+ }
+}