#include #include #include #include #include #include #include #include "../../common/test_helpers_catch2.h" #include using json = nlohmann::json; using yams::ErrorCode; using yams::mcp::ITransport; using yams::mcp::MCPServer; using yams::mcp::MessageResult; using yams::mcp::TransportState; namespace { class NullTransport : public ITransport { public: void send(const json&) override {} MessageResult receive() override { return yams::Error{yams::ErrorCode::NotImplemented, "Null transport"}; } bool isConnected() const override { return false; } void close() override {} TransportState getState() const override { return TransportState::Disconnected; } }; using yams::test::ScopedEnvVar; using yams::test::SpdlogLevelGuard; } // namespace TEST_CASE("MCP routing notifications return notification sentinel errors", "[mcp][routing][notifications][catch2]") { auto server = std::make_shared(std::make_unique()); SECTION("notifications/initialized") { json req = {{"jsonrpc", "2.0"}, {"method", "notifications/initialized"}, {"params", json::object()}}; auto mr = server->handleRequestPublic(req); REQUIRE_FALSE(mr.has_value()); CHECK(mr.error().code == ErrorCode::Success); CHECK(mr.error().message == "notification"); } SECTION("notifications/cancelled accepts requestId") { json req = {{"jsonrpc", "2.0"}, {"method", "notifications/cancelled"}, {"params", {{"requestId", 123}}}}; auto mr = server->handleRequestPublic(req); REQUIRE_FALSE(mr.has_value()); CHECK(mr.error().code == ErrorCode::Success); CHECK(mr.error().message == "notification"); } SECTION("notifications/cancelled rejects missing id") { json req = { {"jsonrpc", "2.0"}, {"method", "notifications/cancelled"}, {"params", json::object()}}; auto mr = server->handleRequestPublic(req); REQUIRE_FALSE(mr.has_value()); CHECK(mr.error().code == ErrorCode::InvalidArgument); } } TEST_CASE("MCP routing logging/setLevel honors extension gating", "[mcp][routing][logging][extensions][catch2]") { SpdlogLevelGuard levelGuard; SECTION("enabled by default returns success result") { ScopedEnvVar clearExt("YAMS_DISABLE_EXTENSIONS", std::nullopt); auto server = std::make_shared(std::make_unique()); json req = {{"jsonrpc", "2.0"}, {"id", 7}, {"method", "logging/setLevel"}, {"params", {{"level", "debug"}}}}; auto mr = server->handleRequestPublic(req); REQUIRE(mr.has_value()); auto out = mr.value(); REQUIRE(out.is_object()); CHECK(out["jsonrpc"] == "2.0"); CHECK(out["id"] == 7); REQUIRE(out.contains("result")); CHECK(out["result"].is_object()); } SECTION("disabled returns method not available error response") { ScopedEnvVar disableExt("YAMS_DISABLE_EXTENSIONS", std::string("1")); auto server = std::make_shared(std::make_unique()); json req = {{"jsonrpc", "2.0"}, {"id", 8}, {"method", "logging/setLevel"}, {"params", {{"level", "warn"}}}}; auto mr = server->handleRequestPublic(req); REQUIRE(mr.has_value()); auto out = mr.value(); REQUIRE(out.is_object()); CHECK(out["jsonrpc"] == "2.0"); CHECK(out["id"] == 8); REQUIRE(out.contains("error")); CHECK(out["error"]["code"] == -32601); REQUIRE(out["error"]["message"].is_string()); CHECK(out["error"]["message"].get().find("extensions disabled") != std::string::npos); } }