diff --git a/src/ScadaLink.Communication/Grpc/SiteStreamGrpcClient.cs b/src/ScadaLink.Communication/Grpc/SiteStreamGrpcClient.cs
new file mode 100644
index 0000000..bfc62b6
--- /dev/null
+++ b/src/ScadaLink.Communication/Grpc/SiteStreamGrpcClient.cs
@@ -0,0 +1,178 @@
+using System.Collections.Concurrent;
+using Grpc.Core;
+using Grpc.Net.Client;
+using Microsoft.Extensions.Logging;
+using ScadaLink.Commons.Messages.Streaming;
+using ScadaLink.Commons.Types.Enums;
+using Google.Protobuf.WellKnownTypes;
+
+namespace ScadaLink.Communication.Grpc;
+
+///
+/// Per-site gRPC client that manages streaming subscriptions to a site's
+/// SiteStreamGrpcServer. The central-side DebugStreamBridgeActor uses this
+/// to open server-streaming calls for individual instances.
+///
+public class SiteStreamGrpcClient : IAsyncDisposable
+{
+ private readonly GrpcChannel? _channel;
+ private readonly SiteStreamService.SiteStreamServiceClient? _client;
+ private readonly ILogger? _logger;
+ private readonly ConcurrentDictionary _subscriptions = new();
+
+ public SiteStreamGrpcClient(string endpoint, ILogger logger)
+ {
+ _channel = GrpcChannel.ForAddress(endpoint, new GrpcChannelOptions
+ {
+ HttpHandler = new SocketsHttpHandler
+ {
+ KeepAlivePingDelay = TimeSpan.FromSeconds(15),
+ KeepAlivePingTimeout = TimeSpan.FromSeconds(10),
+ KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always
+ }
+ });
+ _client = new SiteStreamService.SiteStreamServiceClient(_channel);
+ _logger = logger;
+ }
+
+ ///
+ /// Private constructor for unit testing without a real gRPC channel.
+ ///
+ private SiteStreamGrpcClient()
+ {
+ }
+
+ ///
+ /// Creates a test-only instance that has no gRPC channel. Used to test
+ /// Unsubscribe and Dispose behavior without needing a real endpoint.
+ ///
+ internal static SiteStreamGrpcClient CreateForTesting() => new();
+
+ ///
+ /// Registers a CancellationTokenSource for a correlation ID. Test-only.
+ ///
+ internal void AddSubscriptionForTesting(string correlationId, CancellationTokenSource cts)
+ {
+ _subscriptions[correlationId] = cts;
+ }
+
+ ///
+ /// Opens a server-streaming subscription for a specific instance.
+ /// This is a long-running async method; the caller launches it as a background task.
+ /// The callback delivers domain events, and
+ /// lets the caller handle reconnection.
+ ///
+ public async Task SubscribeAsync(
+ string correlationId,
+ string instanceUniqueName,
+ Action