diff --git a/enterprise/e2e/html/hurl/mcp-2025-03-26.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-03-26.all.hurl
index 2b989ffc..e34980b1 100644
--- a/enterprise/e2e/html/hurl/mcp-2025-03-26.all.hurl
+++ b/enterprise/e2e/html/hurl/mcp-2025-03-26.all.hurl
@@ -338,3 +338,31 @@ POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}}
HTTP 200
[Asserts]
jsonpath "$.valid" == true
+
+POST {{base}}/self/v1/mcp
+MCP-Protocol-Version: 2025-03-26
+Content-Type: application/json
+```
+[ { "jsonrpc": "2.0", "id": 1, "method": "ping" } ]
+```
+HTTP 200
+Content-Type: application/json
+Access-Control-Allow-Origin: http://localhost:8000
+Link: ; rel="describedby"
+[Captures]
+last_response: body
+schema_path: header "Link" regex "<([^>]+)>"
+[Asserts]
+jsonpath "$.jsonrpc" == "2.0"
+jsonpath "$.id" == null
+jsonpath "$.error.code" == 6
+jsonpath "$.error.message" == "Unsupported operation"
+jsonpath "$.error.data" == "Batch operations are not supported in this protocol version yet"
+
+POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}}
+```
+{{last_response}}
+```
+HTTP 200
+[Asserts]
+jsonpath "$.valid" == true
diff --git a/enterprise/e2e/html/hurl/mcp-2025-06-18.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-06-18.all.hurl
index ed860c8e..400308aa 100644
--- a/enterprise/e2e/html/hurl/mcp-2025-06-18.all.hurl
+++ b/enterprise/e2e/html/hurl/mcp-2025-06-18.all.hurl
@@ -219,3 +219,31 @@ POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}}
HTTP 200
[Asserts]
jsonpath "$.valid" == true
+
+POST {{base}}/self/v1/mcp
+MCP-Protocol-Version: 2025-06-18
+Content-Type: application/json
+```
+[ { "jsonrpc": "2.0", "id": 1, "method": "ping" } ]
+```
+HTTP 200
+Content-Type: application/json
+Access-Control-Allow-Origin: http://localhost:8000
+Link: ; rel="describedby"
+[Captures]
+last_response: body
+schema_path: header "Link" regex "<([^>]+)>"
+[Asserts]
+jsonpath "$.jsonrpc" == "2.0"
+jsonpath "$.id" == null
+jsonpath "$.error.code" == -32600
+jsonpath "$.error.message" == "Invalid Request"
+jsonpath "$.error.data" not exists
+
+POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}}
+```
+{{last_response}}
+```
+HTTP 200
+[Asserts]
+jsonpath "$.valid" == true
diff --git a/enterprise/e2e/html/hurl/mcp-2025-11-25-validation.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-11-25-validation.all.hurl
index b43431a8..67f60f21 100644
--- a/enterprise/e2e/html/hurl/mcp-2025-11-25-validation.all.hurl
+++ b/enterprise/e2e/html/hurl/mcp-2025-11-25-validation.all.hurl
@@ -312,3 +312,31 @@ Access-Control-Allow-Origin: http://localhost:8000
[Asserts]
bytes count == 0
header "Link" not exists
+
+POST {{base}}/self/v1/mcp
+MCP-Protocol-Version: 2025-11-25
+Content-Type: application/json
+```
+[ { "jsonrpc": "2.0", "id": 1, "method": "ping" } ]
+```
+HTTP 200
+Content-Type: application/json
+Access-Control-Allow-Origin: http://localhost:8000
+Link: ; rel="describedby"
+[Captures]
+last_response: body
+schema_path: header "Link" regex "<([^>]+)>"
+[Asserts]
+jsonpath "$.jsonrpc" == "2.0"
+jsonpath "$.id" == null
+jsonpath "$.error.code" == -32600
+jsonpath "$.error.message" == "Invalid Request"
+jsonpath "$.error.data" not exists
+
+POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}}
+```
+{{last_response}}
+```
+HTTP 200
+[Asserts]
+jsonpath "$.valid" == true
diff --git a/enterprise/e2e/path/hurl/mcp-2025-03-26.all.hurl b/enterprise/e2e/path/hurl/mcp-2025-03-26.all.hurl
index 514d5693..aa384f2c 100644
--- a/enterprise/e2e/path/hurl/mcp-2025-03-26.all.hurl
+++ b/enterprise/e2e/path/hurl/mcp-2025-03-26.all.hurl
@@ -334,3 +334,31 @@ POST {{base}}/v1/catalog/self/v1/api/schemas/evaluate{{schema_path}}
HTTP 200
[Asserts]
jsonpath "$.valid" == true
+
+POST {{base}}/v1/catalog/self/v1/mcp
+MCP-Protocol-Version: 2025-03-26
+Content-Type: application/json
+```
+[ { "jsonrpc": "2.0", "id": 1, "method": "ping" } ]
+```
+HTTP 200
+Content-Type: application/json
+Access-Control-Allow-Origin: http://localhost:8000
+Link: ; rel="describedby"
+[Captures]
+last_response: body
+schema_path: header "Link" regex "]+)>"
+[Asserts]
+jsonpath "$.jsonrpc" == "2.0"
+jsonpath "$.id" == null
+jsonpath "$.error.code" == 6
+jsonpath "$.error.message" == "Unsupported operation"
+jsonpath "$.error.data" == "Batch operations are not supported in this protocol version yet"
+
+POST {{base}}/v1/catalog/self/v1/api/schemas/evaluate{{schema_path}}
+```
+{{last_response}}
+```
+HTTP 200
+[Asserts]
+jsonpath "$.valid" == true
diff --git a/enterprise/e2e/path/hurl/mcp-2025-06-18.all.hurl b/enterprise/e2e/path/hurl/mcp-2025-06-18.all.hurl
index 4fe471f1..0d393b5d 100644
--- a/enterprise/e2e/path/hurl/mcp-2025-06-18.all.hurl
+++ b/enterprise/e2e/path/hurl/mcp-2025-06-18.all.hurl
@@ -219,3 +219,31 @@ POST {{base}}/v1/catalog/self/v1/api/schemas/evaluate{{schema_path}}
HTTP 200
[Asserts]
jsonpath "$.valid" == true
+
+POST {{base}}/v1/catalog/self/v1/mcp
+MCP-Protocol-Version: 2025-06-18
+Content-Type: application/json
+```
+[ { "jsonrpc": "2.0", "id": 1, "method": "ping" } ]
+```
+HTTP 200
+Content-Type: application/json
+Access-Control-Allow-Origin: http://localhost:8000
+Link: ; rel="describedby"
+[Captures]
+last_response: body
+schema_path: header "Link" regex "]+)>"
+[Asserts]
+jsonpath "$.jsonrpc" == "2.0"
+jsonpath "$.id" == null
+jsonpath "$.error.code" == -32600
+jsonpath "$.error.message" == "Invalid Request"
+jsonpath "$.error.data" not exists
+
+POST {{base}}/v1/catalog/self/v1/api/schemas/evaluate{{schema_path}}
+```
+{{last_response}}
+```
+HTTP 200
+[Asserts]
+jsonpath "$.valid" == true
diff --git a/enterprise/e2e/path/hurl/mcp-2025-11-25-validation.all.hurl b/enterprise/e2e/path/hurl/mcp-2025-11-25-validation.all.hurl
index 58923561..52f21325 100644
--- a/enterprise/e2e/path/hurl/mcp-2025-11-25-validation.all.hurl
+++ b/enterprise/e2e/path/hurl/mcp-2025-11-25-validation.all.hurl
@@ -312,3 +312,31 @@ Access-Control-Allow-Origin: http://localhost:8000
[Asserts]
bytes count == 0
header "Link" not exists
+
+POST {{base}}/v1/catalog/self/v1/mcp
+MCP-Protocol-Version: 2025-11-25
+Content-Type: application/json
+```
+[ { "jsonrpc": "2.0", "id": 1, "method": "ping" } ]
+```
+HTTP 200
+Content-Type: application/json
+Access-Control-Allow-Origin: http://localhost:8000
+Link: ; rel="describedby"
+[Captures]
+last_response: body
+schema_path: header "Link" regex "]+)>"
+[Asserts]
+jsonpath "$.jsonrpc" == "2.0"
+jsonpath "$.id" == null
+jsonpath "$.error.code" == -32600
+jsonpath "$.error.message" == "Invalid Request"
+jsonpath "$.error.data" not exists
+
+POST {{base}}/v1/catalog/self/v1/api/schemas/evaluate{{schema_path}}
+```
+{{last_response}}
+```
+HTTP 200
+[Asserts]
+jsonpath "$.valid" == true
diff --git a/enterprise/server/action_mcp_v1.cc b/enterprise/server/action_mcp_v1.cc
index b6b1d53d..c5e7eca6 100644
--- a/enterprise/server/action_mcp_v1.cc
+++ b/enterprise/server/action_mcp_v1.cc
@@ -185,6 +185,24 @@ auto ActionMCP_v1::on_message(const sourcemeta::one::MCPProtocolVersion version,
return;
}
+ if (request_json.is_array()) {
+ if (version == sourcemeta::one::MCPProtocolVersion::V_2025_03_26) {
+ // TODO: Support batches for strict compliance to MCP 2025-03-26
+ this->write_envelope(
+ request, response, sourcemeta::one::STATUS_OK,
+ sourcemeta::core::jsonrpc_make_error(
+ nullptr, 6, "Unsupported operation",
+ sourcemeta::core::JSON{
+ "Batch operations are not supported in this protocol "
+ "version yet"}));
+ } else {
+ this->write_envelope(
+ request, response, sourcemeta::one::STATUS_OK,
+ sourcemeta::core::jsonrpc_make_error_invalid_request(nullptr));
+ }
+ return;
+ }
+
if (sourcemeta::core::jsonrpc_is_notification(request_json)) {
response.write_status(sourcemeta::one::STATUS_ACCEPTED);
response.write_header("Access-Control-Allow-Origin", this->allowed_origin_);
diff --git a/src/actions/action_mcp_v1.h b/src/actions/action_mcp_v1.h
index f9566d12..108e0b7a 100644
--- a/src/actions/action_mcp_v1.h
+++ b/src/actions/action_mcp_v1.h
@@ -81,9 +81,9 @@ class ActionMCP_v1 : public sourcemeta::one::RouterAction {
return;
}
- if (!sourcemeta::one::mcp_resolve_protocol_version(
- request.header("mcp-protocol-version"))
- .has_value()) {
+ const auto negotiated_version{sourcemeta::one::mcp_resolve_protocol_version(
+ request.header("mcp-protocol-version"))};
+ if (!negotiated_version.has_value()) {
this->write_envelope(request, response,
sourcemeta::one::STATUS_BAD_REQUEST,
sourcemeta::core::jsonrpc_make_error(
@@ -92,9 +92,10 @@ class ActionMCP_v1 : public sourcemeta::one::RouterAction {
}
request.body(
- [this](sourcemeta::one::HTTPRequest &callback_request,
- sourcemeta::one::HTTPResponse &callback_response,
- std::string &&body, bool too_big) {
+ [this, version = negotiated_version.value()](
+ sourcemeta::one::HTTPRequest &callback_request,
+ sourcemeta::one::HTTPResponse &callback_response,
+ std::string &&body, bool too_big) {
if (too_big) {
this->write_envelope(callback_request, callback_response,
sourcemeta::one::STATUS_PAYLOAD_TOO_LARGE,
@@ -111,6 +112,20 @@ class ActionMCP_v1 : public sourcemeta::one::RouterAction {
sourcemeta::core::jsonrpc_make_error_parse());
return;
}
+ if (request_json.is_array()) {
+ // TODO: Support batches for strict compliance to MCP 2025-03-26
+ this->write_envelope(
+ callback_request, callback_response, sourcemeta::one::STATUS_OK,
+ version == sourcemeta::one::MCPProtocolVersion::V_2025_03_26
+ ? sourcemeta::core::jsonrpc_make_error(
+ nullptr, 6, "Unsupported operation",
+ sourcemeta::core::JSON{
+ "Batch operations are not supported in this "
+ "protocol version yet"})
+ : sourcemeta::core::jsonrpc_make_error_invalid_request(
+ nullptr));
+ return;
+ }
this->write_envelope(callback_request, callback_response,
sourcemeta::one::STATUS_OK,
this->on_message(request_json));