From b11886fa68664a73b2660360c98a6a72a6e6acf7 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Sat, 23 May 2026 10:23:14 -0400 Subject: [PATCH] Add tests for unsupported MCP batches Signed-off-by: Juan Cruz Viotti --- .../e2e/html/hurl/mcp-2025-03-26.all.hurl | 28 +++++++++++++++++++ .../e2e/html/hurl/mcp-2025-06-18.all.hurl | 28 +++++++++++++++++++ .../hurl/mcp-2025-11-25-validation.all.hurl | 28 +++++++++++++++++++ .../e2e/path/hurl/mcp-2025-03-26.all.hurl | 28 +++++++++++++++++++ .../e2e/path/hurl/mcp-2025-06-18.all.hurl | 28 +++++++++++++++++++ .../hurl/mcp-2025-11-25-validation.all.hurl | 28 +++++++++++++++++++ enterprise/server/action_mcp_v1.cc | 18 ++++++++++++ src/actions/action_mcp_v1.h | 27 ++++++++++++++---- 8 files changed, 207 insertions(+), 6 deletions(-) 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));