Skip to content

Commit 6435257

Browse files
halter73Copilot
andcommitted
Add stateless MRTR doc pattern coverage tests
Tests that mirror the exact code patterns from mrtr.md and elicitation.md docs in stateless mode: - IsMrtrSupported returns false when client doesn't opt in - IsMrtrSupported check + IncompleteResultException throw (the doc pattern) works end-to-end including ElicitResult.Content access - Same pattern returns fallback when client doesn't opt in - Load shedding (requestState-only) with IsMrtrSupported guard Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 5845866 commit 6435257

1 file changed

Lines changed: 176 additions & 0 deletions

File tree

tests/ModelContextProtocol.AspNetCore.Tests/StatelessMrtrTests.cs

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,4 +467,180 @@ await StartAsync(
467467
var text = Assert.IsType<TextContentBlock>(Assert.Single(result.Content)).Text;
468468
Assert.Equal("True", text);
469469
}
470+
471+
[Fact]
472+
public async Task Stateless_IsMrtrSupported_ReturnsFalse_WhenClientDoesNotOptIn()
473+
{
474+
// When the client doesn't set ExperimentalProtocolVersion, IsMrtrSupported should
475+
// be false even if the server has it configured.
476+
var isMrtrSupportedTool = McpServerTool.Create(
477+
static string (McpServer server) => server.IsMrtrSupported.ToString(),
478+
new McpServerToolCreateOptions
479+
{
480+
Name = "check-mrtr",
481+
Description = "Returns IsMrtrSupported"
482+
});
483+
484+
await StartAsync(
485+
options => options.ExperimentalProtocolVersion = "2026-06-XX",
486+
isMrtrSupportedTool);
487+
488+
// Client does NOT set ExperimentalProtocolVersion
489+
await using var client = await ConnectAsync();
490+
491+
var result = await client.CallToolAsync("check-mrtr",
492+
cancellationToken: TestContext.Current.CancellationToken);
493+
494+
var text = Assert.IsType<TextContentBlock>(Assert.Single(result.Content)).Text;
495+
Assert.Equal("False", text);
496+
}
497+
498+
[Fact]
499+
public async Task Stateless_IsMrtrSupportedCheck_ThenThrow_WorksEndToEnd()
500+
{
501+
// This mirrors the doc pattern: check IsMrtrSupported, return fallback if false,
502+
// throw IncompleteResultException if true. This is the exact code from mrtr.md
503+
// and elicitation.md that was previously untested in stateless mode.
504+
var docPatternTool = McpServerTool.Create(
505+
static string (McpServer server, RequestContext<CallToolRequestParams> context) =>
506+
{
507+
if (context.Params!.InputResponses?.TryGetValue("user_input", out var response) is true)
508+
{
509+
var elicitResult = response.ElicitationResult;
510+
return elicitResult?.Action == "accept"
511+
? $"User accepted: {elicitResult.Content?.FirstOrDefault().Value}"
512+
: "User declined.";
513+
}
514+
515+
if (!server.IsMrtrSupported)
516+
{
517+
return "This tool requires MRTR support.";
518+
}
519+
520+
throw new IncompleteResultException(
521+
inputRequests: new Dictionary<string, InputRequest>
522+
{
523+
["user_input"] = InputRequest.ForElicitation(new ElicitRequestParams
524+
{
525+
Message = "Please confirm",
526+
RequestedSchema = new()
527+
})
528+
},
529+
requestState: "awaiting-confirmation");
530+
},
531+
new McpServerToolCreateOptions
532+
{
533+
Name = "doc-pattern-elicit",
534+
Description = "Mirrors the low-level elicitation doc sample"
535+
});
536+
537+
await StartAsync(
538+
options => options.ExperimentalProtocolVersion = "2026-06-XX",
539+
docPatternTool);
540+
541+
// With MRTR client: should complete the full flow
542+
var mrtrClientOptions = CreateClientOptionsWithAllHandlers();
543+
mrtrClientOptions.ExperimentalProtocolVersion = "2026-06-XX";
544+
545+
await using var mrtrClient = await ConnectAsync(mrtrClientOptions);
546+
547+
var result = await mrtrClient.CallToolAsync("doc-pattern-elicit",
548+
cancellationToken: TestContext.Current.CancellationToken);
549+
550+
var text = Assert.IsType<TextContentBlock>(Assert.Single(result.Content)).Text;
551+
Assert.Equal("User accepted: yes", text);
552+
}
553+
554+
[Fact]
555+
public async Task Stateless_IsMrtrSupportedCheck_ReturnsFallback_WhenClientDoesNotOptIn()
556+
{
557+
// Same doc pattern tool, but the client doesn't opt in. Should return fallback message.
558+
var docPatternTool = McpServerTool.Create(
559+
static string (McpServer server, RequestContext<CallToolRequestParams> context) =>
560+
{
561+
if (context.Params!.InputResponses?.TryGetValue("user_input", out var response) is true)
562+
{
563+
var elicitResult = response.ElicitationResult;
564+
return elicitResult?.Action == "accept"
565+
? $"User accepted: {elicitResult.Content?.FirstOrDefault().Value}"
566+
: "User declined.";
567+
}
568+
569+
if (!server.IsMrtrSupported)
570+
{
571+
return "This tool requires MRTR support.";
572+
}
573+
574+
throw new IncompleteResultException(
575+
inputRequests: new Dictionary<string, InputRequest>
576+
{
577+
["user_input"] = InputRequest.ForElicitation(new ElicitRequestParams
578+
{
579+
Message = "Please confirm",
580+
RequestedSchema = new()
581+
})
582+
},
583+
requestState: "awaiting-confirmation");
584+
},
585+
new McpServerToolCreateOptions
586+
{
587+
Name = "doc-pattern-elicit",
588+
Description = "Mirrors the low-level elicitation doc sample"
589+
});
590+
591+
await StartAsync(
592+
options => options.ExperimentalProtocolVersion = "2026-06-XX",
593+
docPatternTool);
594+
595+
// Client does NOT set ExperimentalProtocolVersion — should get fallback
596+
await using var client = await ConnectAsync();
597+
598+
var result = await client.CallToolAsync("doc-pattern-elicit",
599+
cancellationToken: TestContext.Current.CancellationToken);
600+
601+
var text = Assert.IsType<TextContentBlock>(Assert.Single(result.Content)).Text;
602+
Assert.Equal("This tool requires MRTR support.", text);
603+
}
604+
605+
[Fact]
606+
public async Task Stateless_LoadShedding_RequestStateOnly_CompletesViaMrtr()
607+
{
608+
// Tests the load shedding pattern from mrtr.md — requestState-only IncompleteResult
609+
// without inputRequests. The client should auto-retry with just the requestState.
610+
var loadSheddingTool = McpServerTool.Create(
611+
static string (McpServer server, RequestContext<CallToolRequestParams> context) =>
612+
{
613+
var requestState = context.Params!.RequestState;
614+
if (requestState is not null)
615+
{
616+
return $"resumed:{requestState}";
617+
}
618+
619+
if (!server.IsMrtrSupported)
620+
{
621+
return "MRTR not supported.";
622+
}
623+
624+
throw new IncompleteResultException(requestState: "deferred-work");
625+
},
626+
new McpServerToolCreateOptions
627+
{
628+
Name = "stateless-loadshed",
629+
Description = "Load shedding with IsMrtrSupported check"
630+
});
631+
632+
await StartAsync(
633+
options => options.ExperimentalProtocolVersion = "2026-06-XX",
634+
loadSheddingTool);
635+
636+
var clientOptions = new McpClientOptions { ExperimentalProtocolVersion = "2026-06-XX" };
637+
638+
await using var client = await ConnectAsync(clientOptions);
639+
640+
var result = await client.CallToolAsync("stateless-loadshed",
641+
cancellationToken: TestContext.Current.CancellationToken);
642+
643+
var text = Assert.IsType<TextContentBlock>(Assert.Single(result.Content)).Text;
644+
Assert.Equal("resumed:deferred-work", text);
645+
}
470646
}

0 commit comments

Comments
 (0)