Groupcast cluster server skeleton. (#41132)

* Groupcast cluster server skeleton.

* Code review.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 84eb2a2..3192870 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -103,6 +103,7 @@
       "${chip_root}/src/lib/dnssd/minimal_mdns/responders/tests",
       "${chip_root}/src/lib/dnssd/minimal_mdns/tests",
       "${chip_root}/src/lib/dnssd/tests",
+      "${chip_root}/src/app/clusters/groupcast/tests",
 
       # TODO(#40932): Fix and re-enable the Commodity Tariff unit tests.
       # "${chip_root}/src/app/clusters/commodity-tariff-server/tests",
diff --git a/src/app/clusters/groupcast/BUILD.gn b/src/app/clusters/groupcast/BUILD.gn
new file mode 100644
index 0000000..7f5ce6b
--- /dev/null
+++ b/src/app/clusters/groupcast/BUILD.gn
@@ -0,0 +1,29 @@
+# Copyright (c) 2025 Project CHIP Authors
+#
+# Licensed 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.
+import("//build_overrides/build.gni")
+import("//build_overrides/chip.gni")
+
+source_set("groupcast") {
+  sources = [
+    "GroupcastCluster.cpp",
+    "GroupcastCluster.h",
+    "GroupcastLogic.cpp",
+    "GroupcastLogic.h",
+  ]
+  public_deps = [
+    "${chip_root}/src/app/server",
+    "${chip_root}/src/app/server-cluster",
+    "${chip_root}/zzz_generated/app-common/clusters/Groupcast",
+  ]
+}
diff --git a/src/app/clusters/groupcast/CodegenIntegration.cpp b/src/app/clusters/groupcast/CodegenIntegration.cpp
new file mode 100644
index 0000000..75a6c03
--- /dev/null
+++ b/src/app/clusters/groupcast/CodegenIntegration.cpp
@@ -0,0 +1,103 @@
+/*
+ *    Copyright (c) 2025 Project CHIP Authors
+ *    All rights reserved.
+ *
+ *    Licensed 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.
+ */
+#include <app-common/zap-generated/attributes/Accessors.h>
+#include <app/clusters/groupcast/GroupcastCluster.h>
+#include <app/static-cluster-config/Groupcast.h>
+#include <app/util/attribute-storage.h>
+#include <app/util/endpoint-config-api.h>
+#include <data-model-providers/codegen/ClusterIntegration.h>
+
+using namespace chip::app;
+using namespace chip::app::Clusters;
+using namespace chip::app::Clusters::Groupcast::Attributes;
+using namespace chip::app::Clusters::Groupcast::StaticApplicationConfig;
+using chip::Protocols::InteractionModel::Status;
+
+namespace {
+
+LazyRegisteredServerCluster<GroupcastCluster> gServer;
+
+// Groupcast implementation is specifically implemented
+// only for the root endpoint (endpoint 0)
+
+static constexpr size_t kGroupcastFixedClusterCount = Groupcast::StaticApplicationConfig::kFixedClusterConfig.size();
+
+static_assert((kGroupcastFixedClusterCount == 0) ||
+                  ((kGroupcastFixedClusterCount == 1) &&
+                   Groupcast::StaticApplicationConfig::kFixedClusterConfig[0].endpointNumber == chip::kRootEndpointId),
+              "Groupcast cluster MUST be on endpoint 0");
+
+class IntegrationDelegate : public CodegenClusterIntegration::Delegate
+{
+public:
+    ServerClusterRegistration & CreateRegistration(chip::EndpointId endpointId, unsigned clusterInstanceIndex,
+                                                   uint32_t optionalAttributeBits, uint32_t featureMap) override
+    {
+        // No optional attributes
+        gServer.Create();
+        return gServer.Registration();
+    }
+
+    ServerClusterInterface * FindRegistration(unsigned clusterInstanceIndex) override
+    {
+        VerifyOrReturnValue(gServer.IsConstructed(), nullptr);
+        return &gServer.Cluster();
+    }
+
+    // Nothing to destroy: separate singleton class without constructor/destructor is used
+    void ReleaseRegistration(unsigned clusterInstanceIndex) override { gServer.Destroy(); }
+};
+
+} // namespace
+
+void MatterGroupcastClusterInitCallback(chip::EndpointId endpointId)
+{
+    VerifyOrDie(endpointId == chip::kRootEndpointId);
+
+    IntegrationDelegate integrationDelegate;
+
+    // register a singleton server (root endpoint only)
+    CodegenClusterIntegration::RegisterServer(
+        {
+            .endpointId                = endpointId,
+            .clusterId                 = Groupcast::Id,
+            .fixedClusterInstanceCount = Groupcast::StaticApplicationConfig::kFixedClusterConfig.size(),
+            .maxClusterInstanceCount   = 1, // Cluster is a singleton on the root node and this is the only thing supported
+            .fetchFeatureMap           = false,
+            .fetchOptionalAttributes   = false,
+        },
+        integrationDelegate);
+}
+
+void MatterGroupcastClusterShutdownCallback(chip::EndpointId endpointId)
+{
+    VerifyOrDie(endpointId == chip::kRootEndpointId);
+
+    IntegrationDelegate integrationDelegate;
+
+    CodegenClusterIntegration::UnregisterServer(
+        {
+            .endpointId                = endpointId,
+            .clusterId                 = Groupcast::Id,
+            .fixedClusterInstanceCount = Groupcast::StaticApplicationConfig::kFixedClusterConfig.size(),
+            .maxClusterInstanceCount   = 1, // Cluster is a singleton on the root node and this is the only thing supported
+        },
+        integrationDelegate);
+}
+
+void MatterGroupcastPluginServerInitCallback() {}
+void MatterGroupcastPluginServerShutdownCallback() {}
diff --git a/src/app/clusters/groupcast/GroupcastCluster.cpp b/src/app/clusters/groupcast/GroupcastCluster.cpp
new file mode 100644
index 0000000..7347b7c
--- /dev/null
+++ b/src/app/clusters/groupcast/GroupcastCluster.cpp
@@ -0,0 +1,97 @@
+#include "GroupcastCluster.h"
+#include <app/server-cluster/AttributeListBuilder.h>
+#include <clusters/Groupcast/AttributeIds.h>
+#include <clusters/Groupcast/Attributes.h>
+#include <clusters/Groupcast/Metadata.h>
+
+using chip::Protocols::InteractionModel::Status;
+
+namespace chip {
+namespace app {
+namespace Clusters {
+namespace {
+
+constexpr DataModel::AcceptedCommandEntry kAcceptedCommands[] = {
+    Groupcast::Commands::JoinGroup::kMetadataEntry,
+    Groupcast::Commands::LeaveGroup::kMetadataEntry,
+    Groupcast::Commands::UpdateGroupKey::kMetadataEntry,
+    Groupcast::Commands::ExpireGracePeriod::kMetadataEntry,
+    Groupcast::Commands::ConfigureAuxiliaryACL::kMetadataEntry,
+};
+} // namespace
+
+GroupcastCluster::GroupcastCluster() : DefaultServerCluster({ kRootEndpointId, Groupcast::Id }) {}
+
+DataModel::ActionReturnStatus GroupcastCluster::ReadAttribute(const DataModel::ReadAttributeRequest & request,
+                                                              AttributeValueEncoder & encoder)
+{
+    switch (request.path.mAttributeId)
+    {
+    case Groupcast::Attributes::FeatureMap::Id:
+        return encoder.Encode(mLogic.Features());
+    case Groupcast::Attributes::ClusterRevision::Id:
+        return encoder.Encode(Groupcast::kRevision);
+    case Groupcast::Attributes::Membership::Id:
+        return mLogic.ReadMembership(request.path.mEndpointId, encoder);
+    case Groupcast::Attributes::MaxMembershipCount::Id:
+        return mLogic.ReadMaxMembershipCount(request.path.mEndpointId, encoder);
+    }
+    return Protocols::InteractionModel::Status::UnsupportedAttribute;
+}
+
+CHIP_ERROR GroupcastCluster::Attributes(const ConcreteClusterPath & path,
+                                        ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder)
+{
+    AttributeListBuilder listBuilder(builder);
+    return listBuilder.Append(Span(Groupcast::Attributes::kMandatoryMetadata), {});
+}
+
+std::optional<DataModel::ActionReturnStatus> GroupcastCluster::InvokeCommand(const DataModel::InvokeRequest & request,
+                                                                             chip::TLV::TLVReader & arguments,
+                                                                             CommandHandler * handler)
+{
+    VerifyOrReturnValue(nullptr != handler, Protocols::InteractionModel::Status::InvalidAction);
+    FabricIndex fabric_index = handler->GetAccessingFabricIndex();
+    switch (request.path.mCommandId)
+    {
+    case Groupcast::Commands::JoinGroup::Id: {
+        Groupcast::Commands::JoinGroup::DecodableType data;
+        ReturnErrorOnFailure(data.Decode(arguments, fabric_index));
+        return mLogic.JoinGroup(fabric_index, data);
+    }
+    case Groupcast::Commands::LeaveGroup::Id: {
+        Groupcast::Commands::LeaveGroup::DecodableType data;
+        Groupcast::Commands::LeaveGroupResponse::Type response;
+        ReturnErrorOnFailure(data.Decode(arguments, fabric_index));
+        mLogic.LeaveGroup(fabric_index, data, response);
+        handler->AddResponse(request.path, response);
+        return std::nullopt;
+    }
+    case Groupcast::Commands::UpdateGroupKey::Id: {
+        Groupcast::Commands::UpdateGroupKey::DecodableType data;
+        ReturnErrorOnFailure(data.Decode(arguments, fabric_index));
+        return mLogic.UpdateGroupKey(fabric_index, data);
+    }
+    case Groupcast::Commands::ExpireGracePeriod::Id: {
+        Groupcast::Commands::ExpireGracePeriod::DecodableType data;
+        ReturnErrorOnFailure(data.Decode(arguments, fabric_index));
+        return mLogic.ExpireGracePeriod(fabric_index, data);
+    }
+    case Groupcast::Commands::ConfigureAuxiliaryACL::Id: {
+        Groupcast::Commands::ConfigureAuxiliaryACL::DecodableType data;
+        ReturnErrorOnFailure(data.Decode(arguments, fabric_index));
+        return mLogic.ConfigureAuxiliaryACL(fabric_index, data);
+    }
+    }
+    return Protocols::InteractionModel::Status::UnsupportedCommand;
+}
+
+CHIP_ERROR GroupcastCluster::AcceptedCommands(const ConcreteClusterPath & path,
+                                              ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder)
+{
+    return builder.ReferenceExisting(kAcceptedCommands);
+}
+
+} // namespace Clusters
+} // namespace app
+} // namespace chip
diff --git a/src/app/clusters/groupcast/GroupcastCluster.h b/src/app/clusters/groupcast/GroupcastCluster.h
new file mode 100644
index 0000000..b88bbbc
--- /dev/null
+++ b/src/app/clusters/groupcast/GroupcastCluster.h
@@ -0,0 +1,51 @@
+/*
+ *    Copyright (c) 2025 Project CHIP Authors
+ *    All rights reserved.
+ *
+ *    Licensed 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.
+ */
+#pragma once
+
+#include "GroupcastLogic.h"
+#include <app/server-cluster/DefaultServerCluster.h>
+#include <lib/core/DataModelTypes.h>
+#include <protocols/interaction_model/StatusCode.h>
+
+namespace chip {
+namespace app {
+namespace Clusters {
+
+/**
+ * @brief Provides code-driven implementation for the Groupcast cluster server.
+ */
+class GroupcastCluster : public DefaultServerCluster
+{
+public:
+    GroupcastCluster();
+    virtual ~GroupcastCluster() {}
+
+    DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request,
+                                                AttributeValueEncoder & encoder) override;
+    CHIP_ERROR Attributes(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder) override;
+    std::optional<DataModel::ActionReturnStatus> InvokeCommand(const DataModel::InvokeRequest & request,
+                                                               chip::TLV::TLVReader & arguments, CommandHandler * handler) override;
+    CHIP_ERROR AcceptedCommands(const ConcreteClusterPath & path,
+                                ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder) override;
+
+private:
+    GroupcastLogic mLogic;
+};
+
+} // namespace Clusters
+} // namespace app
+} // namespace chip
diff --git a/src/app/clusters/groupcast/GroupcastLogic.cpp b/src/app/clusters/groupcast/GroupcastLogic.cpp
new file mode 100644
index 0000000..471384a
--- /dev/null
+++ b/src/app/clusters/groupcast/GroupcastLogic.cpp
@@ -0,0 +1,52 @@
+#include "GroupcastLogic.h"
+
+namespace chip {
+namespace app {
+namespace Clusters {
+
+CHIP_ERROR GroupcastLogic::ReadMembership(EndpointId endpoint, AttributeValueEncoder & aEncoder)
+{
+    return CHIP_ERROR_NOT_IMPLEMENTED;
+}
+
+CHIP_ERROR GroupcastLogic::ReadMaxMembershipCount(EndpointId endpoint, AttributeValueEncoder & aEncoder)
+{
+    return CHIP_ERROR_NOT_IMPLEMENTED;
+}
+
+CHIP_ERROR GroupcastLogic::JoinGroup(FabricIndex fabric_index, const Groupcast::Commands::JoinGroup::DecodableType & data)
+{
+    return CHIP_ERROR_NOT_IMPLEMENTED;
+}
+
+CHIP_ERROR GroupcastLogic::LeaveGroup(FabricIndex fabric_index, const Groupcast::Commands::LeaveGroup::DecodableType & data,
+                                      Groupcast::Commands::LeaveGroupResponse::Type & response)
+{
+    return CHIP_ERROR_NOT_IMPLEMENTED;
+}
+
+CHIP_ERROR GroupcastLogic::UpdateGroupKey(FabricIndex fabric_index, const Groupcast::Commands::UpdateGroupKey::DecodableType & data)
+{
+    return CHIP_ERROR_NOT_IMPLEMENTED;
+}
+
+CHIP_ERROR GroupcastLogic::ExpireGracePeriod(FabricIndex fabric_index,
+                                             const Groupcast::Commands::ExpireGracePeriod::DecodableType & data)
+{
+    return CHIP_ERROR_NOT_IMPLEMENTED;
+}
+
+CHIP_ERROR GroupcastLogic::ConfigureAuxiliaryACL(FabricIndex fabric_index,
+                                                 const Groupcast::Commands::ConfigureAuxiliaryACL::DecodableType & data)
+{
+    return CHIP_ERROR_NOT_IMPLEMENTED;
+}
+
+CHIP_ERROR GroupcastLogic::RegisterAccessControl(FabricIndex fabric_index, GroupId group_id)
+{
+    return CHIP_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace Clusters
+} // namespace app
+} // namespace chip
diff --git a/src/app/clusters/groupcast/GroupcastLogic.h b/src/app/clusters/groupcast/GroupcastLogic.h
new file mode 100644
index 0000000..b4b9af7
--- /dev/null
+++ b/src/app/clusters/groupcast/GroupcastLogic.h
@@ -0,0 +1,62 @@
+/*
+ *    Copyright (c) 2025 Project CHIP Authors
+ *    All rights reserved.
+ *
+ *    Licensed 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.
+ */
+#pragma once
+
+#include <app/AttributeValueDecoder.h>
+#include <app/AttributeValueEncoder.h>
+#include <app/data-model-provider/ActionReturnStatus.h>
+#include <clusters/Groupcast/AttributeIds.h>
+#include <clusters/Groupcast/ClusterId.h>
+#include <clusters/Groupcast/CommandIds.h>
+#include <clusters/Groupcast/Commands.h>
+#include <clusters/Groupcast/Enums.h>
+#include <lib/core/CHIPError.h>
+#include <lib/core/DataModelTypes.h>
+
+namespace chip {
+namespace app {
+namespace Clusters {
+
+/**
+ * @brief Implements the Matter specifications for the Groupcast cluster
+ */
+class GroupcastLogic
+{
+public:
+    GroupcastLogic() : mFeatures(0) {}
+    const BitFlags<Groupcast::Feature> & Features() const { return mFeatures; }
+
+    CHIP_ERROR ReadMembership(EndpointId endpoint, AttributeValueEncoder & aEncoder);
+    CHIP_ERROR ReadMaxMembershipCount(EndpointId endpoint, AttributeValueEncoder & aEncoder);
+
+    CHIP_ERROR JoinGroup(FabricIndex fabric_index, const Groupcast::Commands::JoinGroup::DecodableType & data);
+    CHIP_ERROR LeaveGroup(FabricIndex fabric_index, const Groupcast::Commands::LeaveGroup::DecodableType & data,
+                          Groupcast::Commands::LeaveGroupResponse::Type & response);
+    CHIP_ERROR UpdateGroupKey(FabricIndex fabric_index, const Groupcast::Commands::UpdateGroupKey::DecodableType & data);
+    CHIP_ERROR ExpireGracePeriod(FabricIndex fabric_index, const Groupcast::Commands::ExpireGracePeriod::DecodableType & data);
+    CHIP_ERROR ConfigureAuxiliaryACL(FabricIndex fabric_index,
+                                     const Groupcast::Commands::ConfigureAuxiliaryACL::DecodableType & data);
+
+private:
+    CHIP_ERROR RegisterAccessControl(FabricIndex fabric_index, GroupId group_id);
+
+    const BitFlags<Groupcast::Feature> mFeatures;
+};
+
+} // namespace Clusters
+} // namespace app
+} // namespace chip
diff --git a/src/app/clusters/groupcast/app_config_dependent_sources.cmake b/src/app/clusters/groupcast/app_config_dependent_sources.cmake
new file mode 100644
index 0000000..47f3fc8
--- /dev/null
+++ b/src/app/clusters/groupcast/app_config_dependent_sources.cmake
@@ -0,0 +1,30 @@
+# Copyright (c) 2025 Project CHIP Authors
+#
+# Licensed 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.
+
+# This is the equivalent to app_config_dependent_sources.gni
+TARGET_SOURCES(
+  ${APP_TARGET}
+  PRIVATE
+    "${CLUSTER_DIR}/CodegenIntegration.cpp"
+)
+
+# These are the things that BUILD.gn dependencies would pull
+TARGET_SOURCES(
+  ${APP_TARGET}
+  PRIVATE
+    "${CLUSTER_DIR}/GroupcastCluster.cpp"
+    "${CLUSTER_DIR}/GroupcastCluster.h"
+    "${CLUSTER_DIR}/GroupcastLogic.cpp"
+    "${CLUSTER_DIR}/GroupcastLogic.h"
+)
diff --git a/src/app/clusters/groupcast/app_config_dependent_sources.gni b/src/app/clusters/groupcast/app_config_dependent_sources.gni
new file mode 100644
index 0000000..2c3232e
--- /dev/null
+++ b/src/app/clusters/groupcast/app_config_dependent_sources.gni
@@ -0,0 +1,14 @@
+# Copyright (c) 2025 Project CHIP Authors
+#
+# Licensed 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.
+app_config_dependent_sources = [ "CodegenIntegration.cpp" ]
diff --git a/src/app/clusters/groupcast/tests/BUILD.gn b/src/app/clusters/groupcast/tests/BUILD.gn
new file mode 100644
index 0000000..ecd0771
--- /dev/null
+++ b/src/app/clusters/groupcast/tests/BUILD.gn
@@ -0,0 +1,33 @@
+# Copyright (c) 2025 Project CHIP Authors
+#
+# Licensed 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.
+
+import("//build_overrides/build.gni")
+import("//build_overrides/chip.gni")
+
+import("${chip_root}/build/chip/chip_test_suite.gni")
+
+chip_test_suite("tests") {
+  output_name = "libTestGroupcastCluster"
+
+  test_sources = [ "TestGroupcastCluster.cpp" ]
+
+  cflags = [ "-Wconversion" ]
+
+  public_deps = [
+    "${chip_root}/src/app/clusters/groupcast",
+    "${chip_root}/src/app/clusters/testing",
+    "${chip_root}/src/lib/core:string-builder-adapters",
+    "${chip_root}/src/lib/support",
+  ]
+}
diff --git a/src/app/clusters/groupcast/tests/TestGroupcastCluster.cpp b/src/app/clusters/groupcast/tests/TestGroupcastCluster.cpp
new file mode 100644
index 0000000..ea5308d
--- /dev/null
+++ b/src/app/clusters/groupcast/tests/TestGroupcastCluster.cpp
@@ -0,0 +1,65 @@
+/*
+ *    Copyright (c) 2025 Project CHIP Authors
+ *
+ *    Licensed 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.
+ */
+#include <pw_unit_test/framework.h>
+
+#include <app/clusters/groupcast/GroupcastCluster.h>
+#include <app/clusters/testing/AttributeTesting.h>
+#include <app/data-model-provider/MetadataTypes.h>
+#include <app/server-cluster/DefaultServerCluster.h>
+#include <clusters/Groupcast/Enums.h>
+#include <clusters/Groupcast/Metadata.h>
+#include <lib/core/CHIPError.h>
+#include <lib/core/DataModelTypes.h>
+#include <lib/support/BitFlags.h>
+#include <lib/support/ReadOnlyBuffer.h>
+#include <platform/NetworkCommissioning.h>
+
+namespace {
+
+using namespace chip;
+using namespace chip::app::Clusters::Groupcast;
+
+using chip::app::DataModel::AcceptedCommandEntry;
+using chip::app::DataModel::AttributeEntry;
+
+// initialize memory as ReadOnlyBufferBuilder may allocate
+struct TestGroupcastCluster : public ::testing::Test
+{
+    static void SetUpTestSuite() { ASSERT_EQ(Platform::MemoryInit(), CHIP_NO_ERROR); }
+    static void TearDownTestSuite() { Platform::MemoryShutdown(); }
+};
+
+TEST_F(TestGroupcastCluster, TestAttributes)
+{
+    app::Clusters::GroupcastCluster cluster;
+
+    // Attributes
+    {
+        ReadOnlyBufferBuilder<AttributeEntry> builder;
+        ASSERT_EQ(cluster.Attributes({ kRootEndpointId, chip::app::Clusters::Groupcast::Id }, builder), CHIP_NO_ERROR);
+
+        ReadOnlyBufferBuilder<AttributeEntry> expectedBuilder;
+        ASSERT_EQ(expectedBuilder.AppendElements({
+                      Attributes::Membership::kMetadataEntry,
+                      Attributes::MaxMembershipCount::kMetadataEntry,
+                  }),
+                  CHIP_NO_ERROR);
+        ASSERT_EQ(expectedBuilder.ReferenceExisting(app::DefaultServerCluster::GlobalAttributes()), CHIP_NO_ERROR);
+        ASSERT_TRUE(Testing::EqualAttributeSets(builder.TakeBuffer(), expectedBuilder.TakeBuffer()));
+    }
+}
+
+} // namespace
diff --git a/src/app/common/templates/config-data.yaml b/src/app/common/templates/config-data.yaml
index bec9bca..c5075a7 100644
--- a/src/app/common/templates/config-data.yaml
+++ b/src/app/common/templates/config-data.yaml
@@ -50,6 +50,7 @@
     - General Commissioning
     - General Diagnostics
     - Group Key Management
+    - Groupcast
     - HEPA Filter Monitoring
     - Joint Fabric Administrator
     - Laundry Washer Mode
@@ -152,6 +153,7 @@
     - General Commissioning
     - General Diagnostics
     - Group Key Management
+    - Groupcast
     - Localization Configuration
     - OTA Software Update Provider
     - Operational Credentials
diff --git a/src/app/zap_cluster_list.json b/src/app/zap_cluster_list.json
index 6dfb44e..f9a5780 100644
--- a/src/app/zap_cluster_list.json
+++ b/src/app/zap_cluster_list.json
@@ -216,7 +216,7 @@
         "ELECTRICAL_GRID_CONDITIONS_CLUSTER": [
             "electrical-grid-conditions-server"
         ],
-        "GROUPCAST_CLUSTER": [],
+        "GROUPCAST_CLUSTER": ["groupcast-server"],
         "METER_IDENTIFICATION_CLUSTER": ["meter-identification-server"],
         "MICROWAVE_OVEN_MODE_CLUSTER": ["mode-base-server"],
         "DOOR_LOCK_CLUSTER": ["door-lock-server"],
diff --git a/zzz_generated/app-common/app-common/zap-generated/callback.h b/zzz_generated/app-common/app-common/zap-generated/callback.h
index a73f5f0..702eeed 100644
--- a/zzz_generated/app-common/app-common/zap-generated/callback.h
+++ b/zzz_generated/app-common/app-common/zap-generated/callback.h
@@ -7149,36 +7149,6 @@
     chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
     const chip::app::Clusters::DishwasherAlarm::Commands::ModifyEnabledAlarms::DecodableType & commandData);
 /**
- * @brief Groupcast Cluster JoinGroup Command callback (from client)
- */
-bool emberAfGroupcastClusterJoinGroupCallback(
-    chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
-    const chip::app::Clusters::Groupcast::Commands::JoinGroup::DecodableType & commandData);
-/**
- * @brief Groupcast Cluster LeaveGroup Command callback (from client)
- */
-bool emberAfGroupcastClusterLeaveGroupCallback(
-    chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
-    const chip::app::Clusters::Groupcast::Commands::LeaveGroup::DecodableType & commandData);
-/**
- * @brief Groupcast Cluster UpdateGroupKey Command callback (from client)
- */
-bool emberAfGroupcastClusterUpdateGroupKeyCallback(
-    chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
-    const chip::app::Clusters::Groupcast::Commands::UpdateGroupKey::DecodableType & commandData);
-/**
- * @brief Groupcast Cluster ExpireGracePeriod Command callback (from client)
- */
-bool emberAfGroupcastClusterExpireGracePeriodCallback(
-    chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
-    const chip::app::Clusters::Groupcast::Commands::ExpireGracePeriod::DecodableType & commandData);
-/**
- * @brief Groupcast Cluster ConfigureAuxiliaryACL Command callback (from client)
- */
-bool emberAfGroupcastClusterConfigureAuxiliaryACLCallback(
-    chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
-    const chip::app::Clusters::Groupcast::Commands::ConfigureAuxiliaryACL::DecodableType & commandData);
-/**
  * @brief Boolean State Configuration Cluster SuppressAlarm Command callback (from client)
  */
 bool emberAfBooleanStateConfigurationClusterSuppressAlarmCallback(