Skip to content

Commit 3aeb12b

Browse files
author
Nicholas Blumhardt
committed
Imporove signal list tests, use structured tool results for signal listing
1 parent b012068 commit 3aeb12b

3 files changed

Lines changed: 25 additions & 11 deletions

File tree

src/SeqCli/Mcp/Tools/Search/SearchAndQueryToolType.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ public Task NewSessionAsync(CancellationToken cancellationToken)
331331
return Task.CompletedTask;
332332
}
333333

334-
[McpServerTool(Name = "seq_list_signals", ReadOnly = true, Title = "List Signals")]
334+
[McpServerTool(Name = "seq_list_signals", ReadOnly = true, Title = "List Signals", UseStructuredContent = true)]
335335
[Description("List available signals. Use signals when searching and querying to efficiently work with well-known " +
336336
"event streams while dramatically improving response times.")]
337337
public async Task<SignalSummary[]> ListSignalsAsync(CancellationToken cancellationToken)

test/SeqCli.EndToEnd/Mcp/McpSignalUsageTestCase.cs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ namespace SeqCli.EndToEnd.Mcp;
1212
// ReSharper disable once UnusedType.Global
1313
public class McpSignalUsageTestCase : McpToolTestCase
1414
{
15+
record SignalSummary(string Id, string Title);
16+
1517
// Default signals included in every Seq installation.
16-
const string Errors = "signal-m33301";
17-
const string Warnings = "signal-m33302";
18-
const string Spans = "signal-m20231011";
19-
const string Logs = "signal-m20231211";
18+
static readonly SignalSummary Errors = new("signal-m33301", "Errors");
19+
static readonly SignalSummary Warnings = new("signal-m33302", "Warnings");
20+
static readonly SignalSummary Spans = new("signal-m20231011", "Spans");
21+
static readonly SignalSummary Logs = new("signal-m20231211", "Logs");
2022

2123
protected override async Task ExecuteAsync(SeqConnection connection, ILogger logger, McpClient client)
2224
{
@@ -28,27 +30,27 @@ protected override async Task ExecuteAsync(SeqConnection connection, ILogger log
2830
logger.Warning("Item {ItemNumber} delayed in run {RunId}", 4, runId);
2931
logger.Error("Item {ItemNumber} failed in run {RunId}", 5, runId);
3032

31-
var signalsResult = AssertTextResult(await client.CallToolAsync("seq_list_signals"));
33+
var signals = AssertStructuredResult<SignalSummary[]>(await client.CallToolAsync("seq_list_signals"));
3234
foreach (var signal in new[] { Errors, Warnings, Spans, Logs })
3335
{
34-
Assert.Contains(signal, signalsResult);
36+
Assert.Contains(signal, signals);
3537
}
3638

3739
var predicate = $"RunId = '{runId}' and @Timestamp >= Now() - 1d";
3840

3941
// Union: the two warnings plus the error.
40-
Assert.Equal(3, await CountSearchResultsAsync(client, predicate, $"{Errors}~{Warnings}"));
42+
Assert.Equal(3, await CountSearchResultsAsync(client, predicate, $"{Errors.Id}~{Warnings.Id}"));
4143

4244
// Intersection: all of the warnings are log events, not spans.
43-
Assert.Equal(2, await CountSearchResultsAsync(client, predicate, $"{Warnings},{Logs}"));
45+
Assert.Equal(2, await CountSearchResultsAsync(client, predicate, $"{Warnings.Id},{Logs.Id}"));
4446

4547
var query = $"select count(*) as total from stream where {predicate}";
4648

4749
// Union: no spans were written, so only the error is counted.
48-
Assert.Equal(1, await CountQueryResultAsync(client, query, $"{Spans}~{Errors}"));
50+
Assert.Equal(1, await CountQueryResultAsync(client, query, $"{Spans.Id}~{Errors.Id}"));
4951

5052
// Intersection with a grouped union: warnings and errors, all of which are log events.
51-
Assert.Equal(3, await CountQueryResultAsync(client, query, $"({Errors}~{Warnings}),{Logs}"));
53+
Assert.Equal(3, await CountQueryResultAsync(client, query, $"({Errors.Id}~{Warnings.Id}),{Logs.Id}"));
5254
}
5355

5456
static async Task<int> CountSearchResultsAsync(McpClient client, string predicate, string signal)

test/SeqCli.EndToEnd/Mcp/McpToolTestCase.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Linq;
2+
using System.Text.Json;
23
using System.Text.RegularExpressions;
34
using System.Threading.Tasks;
45
using ModelContextProtocol.Client;
@@ -39,6 +40,17 @@ protected static string AssertTextResult(CallToolResult callToolResult)
3940
return text;
4041
}
4142

43+
protected static T AssertStructuredResult<T>(CallToolResult callToolResult)
44+
{
45+
AssertTextResult(callToolResult);
46+
Assert.NotNull(callToolResult.StructuredContent);
47+
48+
// Tools returning non-object values have them wrapped in a `result` property by the MCP
49+
// SDK, because the protocol requires `structuredContent` to be an object.
50+
var result = callToolResult.StructuredContent.Value.GetProperty("result");
51+
return JsonSerializer.Deserialize<T>(result, JsonSerializerOptions.Web)!;
52+
}
53+
4254
protected static string[] OrderedSearchResultIds(string searchResult)
4355
{
4456
return ResultIdRegex().Matches(searchResult).Select(m => m.Value).Distinct().ToArray();

0 commit comments

Comments
 (0)