diff --git a/src/NATS.Server/Monitoring/Connz.cs b/src/NATS.Server/Monitoring/Connz.cs
index 0043484..2f0f214 100644
--- a/src/NATS.Server/Monitoring/Connz.cs
+++ b/src/NATS.Server/Monitoring/Connz.cs
@@ -163,6 +163,143 @@ public sealed class SubDetail
public ulong Cid { get; set; }
}
+///
+/// Lightweight connection info record used by the account-scoped filter API.
+/// Decoupled from so filter logic can be tested without a running server.
+/// Go reference: monitor.go ConnInfo (subset of fields relevant to account filtering).
+///
+public sealed record ConnzConnectionInfo(
+ ulong ClientId,
+ string RemoteAddress,
+ string? AccountName,
+ string? Name,
+ DateTime ConnectedAt,
+ long InMsgs,
+ long OutMsgs,
+ long InBytes,
+ long OutBytes);
+
+///
+/// Paginated result returned by .
+///
+public sealed record ConnzFilterResult(
+ IReadOnlyList Connections,
+ int Total,
+ int Offset,
+ int Limit);
+
+///
+/// Query-string options for the account-scoped filter API.
+/// Parses the ?acc=, ?state=, ?offset=, and ?limit= parameters that the Go server
+/// accepts on the /connz endpoint.
+/// Go reference: monitor.go ConnzOptions / Connz().
+///
+public sealed class ConnzFilterOptions
+{
+ public string? AccountFilter { get; init; }
+
+ /// "open", "closed", or "any" (default: "open")
+ public string? StateFilter { get; init; }
+
+ public int Offset { get; init; }
+
+ public int Limit { get; init; } = 1024;
+
+ ///
+ /// Parses a raw query string (e.g. "?acc=ACCOUNT&state=open&offset=0&limit=100")
+ /// into a instance.
+ ///
+ public static ConnzFilterOptions Parse(string? queryString)
+ {
+ if (string.IsNullOrEmpty(queryString))
+ return new ConnzFilterOptions();
+
+ // Strip leading '?'
+ var qs = queryString.TrimStart('?');
+
+ string? accountFilter = null;
+ string? stateFilter = null;
+ int offset = 0;
+ int limit = 1024;
+
+ foreach (var pair in qs.Split('&', StringSplitOptions.RemoveEmptyEntries))
+ {
+ var eqIdx = pair.IndexOf('=');
+ if (eqIdx < 0) continue;
+
+ var key = Uri.UnescapeDataString(pair[..eqIdx]).ToLowerInvariant();
+ var value = Uri.UnescapeDataString(pair[(eqIdx + 1)..]);
+
+ switch (key)
+ {
+ case "acc":
+ accountFilter = value;
+ break;
+ case "state":
+ stateFilter = value.ToLowerInvariant();
+ break;
+ case "offset" when int.TryParse(value, out var o):
+ offset = o;
+ break;
+ case "limit" when int.TryParse(value, out var l):
+ limit = l;
+ break;
+ }
+ }
+
+ return new ConnzFilterOptions
+ {
+ AccountFilter = accountFilter,
+ StateFilter = stateFilter,
+ Offset = offset,
+ Limit = limit,
+ };
+ }
+}
+
+///
+/// Pure filtering helper for collections.
+/// Provides account-scoped filtering and pagination that mirror the Go server's
+/// /connz ?acc= behavior.
+/// Go reference: monitor.go Connz() — account filter branch.
+///
+public static class ConnzFilter
+{
+ ///
+ /// Filters to only those whose
+ /// matches
+ /// using a case-insensitive ordinal comparison.
+ ///
+ public static IReadOnlyList FilterByAccount(
+ IEnumerable connections,
+ string accountName)
+ {
+ return connections
+ .Where(c => string.Equals(c.AccountName, accountName, StringComparison.OrdinalIgnoreCase))
+ .ToList();
+ }
+
+ ///
+ /// Applies all filters specified in and returns a paginated result.
+ ///
+ public static ConnzFilterResult ApplyFilters(
+ IEnumerable connections,
+ ConnzFilterOptions options)
+ {
+ IEnumerable filtered = connections;
+
+ if (!string.IsNullOrEmpty(options.AccountFilter))
+ filtered = filtered.Where(c =>
+ string.Equals(c.AccountName, options.AccountFilter, StringComparison.OrdinalIgnoreCase));
+
+ var list = filtered.ToList();
+ var total = list.Count;
+ var paged = list.Skip(options.Offset).Take(options.Limit).ToList();
+
+ return new ConnzFilterResult(paged, total, options.Offset, options.Limit);
+ }
+}
+
///
/// Sort options for connection listing.
/// Corresponds to Go server/monitor_sort_opts.go SortOpt type.
diff --git a/tests/NATS.Server.Tests/Monitoring/ConnzAccountFilterTests.cs b/tests/NATS.Server.Tests/Monitoring/ConnzAccountFilterTests.cs
new file mode 100644
index 0000000..786069c
--- /dev/null
+++ b/tests/NATS.Server.Tests/Monitoring/ConnzAccountFilterTests.cs
@@ -0,0 +1,190 @@
+using NATS.Server.Monitoring;
+
+namespace NATS.Server.Tests.Monitoring;
+
+///
+/// Unit tests for account-scoped /connz filtering (Gap 10.2).
+/// Exercises ConnzFilterOptions.Parse, ConnzFilter.FilterByAccount, and
+/// ConnzFilter.ApplyFilters in isolation — no running server required.
+/// Go reference: monitor_test.go — TestConnzFilterByAccount, TestConnzWithAccount.
+///
+public class ConnzAccountFilterTests
+{
+ // -----------------------------------------------------------------------
+ // Helper — build a ConnzConnectionInfo with sensible defaults
+ // -----------------------------------------------------------------------
+
+ private static ConnzConnectionInfo MakeConn(
+ ulong clientId,
+ string? accountName,
+ string remoteAddress = "127.0.0.1:1234",
+ string? name = null,
+ long inMsgs = 0,
+ long outMsgs = 0,
+ long inBytes = 0,
+ long outBytes = 0) =>
+ new(clientId, remoteAddress, accountName, name, DateTime.UtcNow,
+ inMsgs, outMsgs, inBytes, outBytes);
+
+ // -----------------------------------------------------------------------
+ // FilterByAccount tests
+ // -----------------------------------------------------------------------
+
+ [Fact]
+ public void FilterByAccount_MatchingAccount_ReturnsFiltered()
+ {
+ // Go reference: monitor.go Connz() — "if opts.Account != "" { ... }" filter
+ var connections = new[]
+ {
+ MakeConn(1, "acctA"),
+ MakeConn(2, "acctB"),
+ MakeConn(3, "acctA"),
+ };
+
+ var result = ConnzFilter.FilterByAccount(connections, "acctA");
+
+ result.Count.ShouldBe(2);
+ result.ShouldAllBe(c => c.AccountName == "acctA");
+ }
+
+ [Fact]
+ public void FilterByAccount_NoMatch_ReturnsEmpty()
+ {
+ var connections = new[]
+ {
+ MakeConn(1, "acctA"),
+ MakeConn(2, "acctB"),
+ };
+
+ var result = ConnzFilter.FilterByAccount(connections, "acctC");
+
+ result.ShouldBeEmpty();
+ }
+
+ [Fact]
+ public void FilterByAccount_CaseInsensitive()
+ {
+ // Go reference: monitor.go — account name comparison is case-insensitive
+ var connections = new[]
+ {
+ MakeConn(1, "AcctA"),
+ MakeConn(2, "acctb"),
+ };
+
+ // Upper-case query against lower-case stored name
+ var lowerResult = ConnzFilter.FilterByAccount(connections, "accta");
+ lowerResult.Count.ShouldBe(1);
+ lowerResult[0].ClientId.ShouldBe(1UL);
+
+ // Lower-case query against mixed-case stored name
+ var upperResult = ConnzFilter.FilterByAccount(connections, "ACCTB");
+ upperResult.Count.ShouldBe(1);
+ upperResult[0].ClientId.ShouldBe(2UL);
+ }
+
+ // -----------------------------------------------------------------------
+ // ConnzFilterOptions.Parse tests
+ // -----------------------------------------------------------------------
+
+ [Fact]
+ public void Parse_WithAccParam_SetsAccountFilter()
+ {
+ var opts = ConnzFilterOptions.Parse("?acc=myAccount");
+
+ opts.AccountFilter.ShouldBe("myAccount");
+ }
+
+ [Fact]
+ public void Parse_WithoutParams_DefaultValues()
+ {
+ var opts = ConnzFilterOptions.Parse(null);
+
+ opts.AccountFilter.ShouldBeNull();
+ opts.StateFilter.ShouldBeNull();
+ opts.Offset.ShouldBe(0);
+ opts.Limit.ShouldBe(1024);
+ }
+
+ [Fact]
+ public void Parse_WithOffsetAndLimit_ParsesCorrectly()
+ {
+ var opts = ConnzFilterOptions.Parse("?acc=acctA&state=open&offset=10&limit=50");
+
+ opts.AccountFilter.ShouldBe("acctA");
+ opts.StateFilter.ShouldBe("open");
+ opts.Offset.ShouldBe(10);
+ opts.Limit.ShouldBe(50);
+ }
+
+ // -----------------------------------------------------------------------
+ // ApplyFilters tests
+ // -----------------------------------------------------------------------
+
+ [Fact]
+ public void ApplyFilters_WithAccountFilter_FiltersCorrectly()
+ {
+ var connections = new[]
+ {
+ MakeConn(1, "acctA"),
+ MakeConn(2, "acctB"),
+ MakeConn(3, "acctA"),
+ };
+
+ var opts = new ConnzFilterOptions { AccountFilter = "acctA" };
+ var result = ConnzFilter.ApplyFilters(connections, opts);
+
+ result.Total.ShouldBe(2);
+ result.Connections.Count.ShouldBe(2);
+ result.Connections.ShouldAllBe(c => c.AccountName == "acctA");
+ }
+
+ [Fact]
+ public void ApplyFilters_WithOffset_SkipsEntries()
+ {
+ var connections = Enumerable.Range(1, 5)
+ .Select(i => MakeConn((ulong)i, "acctA"))
+ .ToList();
+
+ var opts = new ConnzFilterOptions { Offset = 3, Limit = 10 };
+ var result = ConnzFilter.ApplyFilters(connections, opts);
+
+ result.Total.ShouldBe(5);
+ result.Connections.Count.ShouldBe(2);
+ result.Offset.ShouldBe(3);
+ // The paged items should be the last two (clientId 4 and 5)
+ result.Connections[0].ClientId.ShouldBe(4UL);
+ result.Connections[1].ClientId.ShouldBe(5UL);
+ }
+
+ [Fact]
+ public void ApplyFilters_WithLimit_CapsResults()
+ {
+ var connections = Enumerable.Range(1, 10)
+ .Select(i => MakeConn((ulong)i, "acctA"))
+ .ToList();
+
+ var opts = new ConnzFilterOptions { Limit = 3 };
+ var result = ConnzFilter.ApplyFilters(connections, opts);
+
+ result.Total.ShouldBe(10);
+ result.Connections.Count.ShouldBe(3);
+ result.Limit.ShouldBe(3);
+ }
+
+ [Fact]
+ public void ApplyFilters_NoFilters_ReturnsAll()
+ {
+ var connections = new[]
+ {
+ MakeConn(1, "acctA"),
+ MakeConn(2, "acctB"),
+ MakeConn(3, null),
+ };
+
+ var opts = new ConnzFilterOptions();
+ var result = ConnzFilter.ApplyFilters(connections, opts);
+
+ result.Total.ShouldBe(3);
+ result.Connections.Count.ShouldBe(3);
+ }
+}