Skip to content

Commit 7235615

Browse files
halter73Copilot
andcommitted
Add SEP-2322 MRTR conformance scenarios (ephemeral)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 560d064 commit 7235615

5 files changed

Lines changed: 415 additions & 0 deletions

File tree

tests/Common/Utils/NodeHelpers.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,44 @@ public static bool HasSep2243Scenarios()
205205
}
206206
}
207207

208+
/// <summary>
209+
/// Checks whether the SEP-2322 (Multi Round-Trip Requests / IncompleteResult)
210+
/// conformance scenarios are available by reading the conformance package version
211+
/// from the repo's package.json. MRTR scenarios require a conformance package version
212+
/// that includes SEP-2322 support (see
213+
/// https://github.com/modelcontextprotocol/conformance/pull/188).
214+
/// </summary>
215+
public static bool HasMrtrScenarios()
216+
{
217+
try
218+
{
219+
var repoRoot = FindRepoRoot();
220+
var packageJsonPath = Path.Combine(repoRoot, "package.json");
221+
if (!File.Exists(packageJsonPath))
222+
{
223+
return false;
224+
}
225+
226+
var json = System.Text.Json.JsonDocument.Parse(File.ReadAllText(packageJsonPath));
227+
if (json.RootElement.TryGetProperty("dependencies", out var deps) &&
228+
deps.TryGetProperty("@modelcontextprotocol/conformance", out var versionElement))
229+
{
230+
var versionStr = versionElement.GetString();
231+
if (versionStr is not null && Version.TryParse(versionStr, out var version))
232+
{
233+
// SEP-2322 scenarios are expected in conformance package >= 0.2.0
234+
return version >= new Version(0, 2, 0);
235+
}
236+
}
237+
238+
return false;
239+
}
240+
catch
241+
{
242+
return false;
243+
}
244+
}
245+
208246
private static ProcessStartInfo NpmStartInfo(string arguments, string workingDirectory)
209247
{
210248
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))

tests/ModelContextProtocol.AspNetCore.Tests/ServerConformanceTests.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,34 @@ public async Task RunConformanceTest_HttpCustomHeaderServerValidation()
159159
$"Conformance test failed.\n\nStdout:\n{result.Output}\n\nStderr:\n{result.Error}");
160160
}
161161

162+
// SEP-2322 (Multi Round-Trip Requests / IncompleteResult) conformance scenarios.
163+
// The csharp-sdk ConformanceServer surfaces the matching tools/prompts via
164+
// ConformanceServer.Tools.IncompleteResultTools and ConformanceServer.Prompts.IncompleteResultPrompts.
165+
// Each scenario uses the conformance harness's RawMcpSession, which negotiates DRAFT-2026-v1
166+
// so the csharp-sdk emits InputRequiredResult on the wire. These tests skip until the
167+
// upstream conformance package ships with SEP-2322 scenarios
168+
// (https://github.com/modelcontextprotocol/conformance/pull/188).
169+
[Theory]
170+
[InlineData("incomplete-result-basic-elicitation")]
171+
[InlineData("incomplete-result-basic-sampling")]
172+
[InlineData("incomplete-result-basic-list-roots")]
173+
[InlineData("incomplete-result-request-state")]
174+
[InlineData("incomplete-result-multiple-input-requests")]
175+
[InlineData("incomplete-result-multi-round")]
176+
[InlineData("incomplete-result-missing-input-response")]
177+
[InlineData("incomplete-result-non-tool-request")]
178+
public async Task RunMrtrConformanceTest(string scenario)
179+
{
180+
Assert.SkipWhen(!NodeHelpers.IsNodeInstalled(), "Node.js is not installed. Skipping conformance tests.");
181+
Assert.SkipWhen(!NodeHelpers.HasMrtrScenarios(), "SEP-2322 MRTR conformance scenarios not yet available in the published @modelcontextprotocol/conformance package.");
182+
183+
var result = await RunConformanceTestsAsync(
184+
$"server --url {fixture.ServerUrl} --scenario {scenario}");
185+
186+
Assert.True(result.Success,
187+
$"MRTR conformance test '{scenario}' failed.\n\nStdout:\n{result.Output}\n\nStderr:\n{result.Error}");
188+
}
189+
162190
private async Task<(bool Success, string Output, string Error)> RunConformanceTestsAsync(string arguments)
163191
{
164192
var startInfo = NodeHelpers.ConformanceTestStartInfo(arguments);

tests/ModelContextProtocol.ConformanceServer/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public static async Task MainAsync(string[] args, ILoggerProvider? loggerProvide
3131
.WithHttpTransport()
3232
.WithDistributedCacheEventStreamStore()
3333
.WithTools<ConformanceTools>()
34+
.WithTools<IncompleteResultTools>()
3435
.WithTools([ConformanceTools.CreateJsonSchema202012Tool()])
3536
.WithRequestFilters(filters => filters.AddCallToolFilter(next => async (request, cancellationToken) =>
3637
{
@@ -47,6 +48,7 @@ public static async Task MainAsync(string[] args, ILoggerProvider? loggerProvide
4748
return result;
4849
}))
4950
.WithPrompts<ConformancePrompts>()
51+
.WithPrompts<IncompleteResultPrompts>()
5052
.WithResources<ConformanceResources>()
5153
.WithSubscribeToResourcesHandler(async (ctx, ct) =>
5254
{
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#pragma warning disable MCPEXP001 // MRTR (SEP-2322) is experimental.
2+
3+
using ModelContextProtocol;
4+
using ModelContextProtocol.Protocol;
5+
using ModelContextProtocol.Server;
6+
using System.ComponentModel;
7+
using System.Text.Json;
8+
9+
namespace ConformanceServer.Prompts;
10+
11+
/// <summary>
12+
/// Prompt implementing the SEP-2322 D1 conformance scenario (<c>incomplete-result-non-tool-request</c>),
13+
/// proving that <c>prompts/get</c> can return an <see cref="InputRequiredResult"/> just like
14+
/// <c>tools/call</c>.
15+
/// </summary>
16+
[McpServerPromptType]
17+
public sealed class IncompleteResultPrompts
18+
{
19+
[McpServerPrompt(Name = "test_incomplete_result_prompt")]
20+
[Description("SEP-2322 D1: prompts/get returns IncompleteResult until user_context is supplied.")]
21+
public static GetPromptResult IncompleteResultPrompt(RequestContext<GetPromptRequestParams> context)
22+
{
23+
if (context.Params!.InputResponses is { } responses &&
24+
responses.TryGetValue("user_context", out var response))
25+
{
26+
var elicit = response.ElicitationResult;
27+
var contextValue = TryReadString(elicit?.Content, "context") ?? "(unknown)";
28+
return new GetPromptResult
29+
{
30+
Description = "Prompt customized with elicited user context.",
31+
Messages =
32+
[
33+
new PromptMessage
34+
{
35+
Role = Role.User,
36+
Content = new TextContentBlock { Text = $"Please continue using context: {contextValue}" },
37+
},
38+
],
39+
};
40+
}
41+
42+
throw new InputRequiredException(
43+
new Dictionary<string, InputRequest>
44+
{
45+
["user_context"] = InputRequest.ForElicitation(new ElicitRequestParams
46+
{
47+
Message = "What context should the prompt use?",
48+
RequestedSchema = new ElicitRequestParams.RequestSchema
49+
{
50+
Properties = new Dictionary<string, ElicitRequestParams.PrimitiveSchemaDefinition>
51+
{
52+
["context"] = new ElicitRequestParams.StringSchema(),
53+
},
54+
Required = ["context"],
55+
},
56+
}),
57+
});
58+
}
59+
60+
private static string? TryReadString(IDictionary<string, JsonElement>? content, string key)
61+
{
62+
if (content is null || !content.TryGetValue(key, out var element))
63+
{
64+
return null;
65+
}
66+
return element.ValueKind == JsonValueKind.String ? element.GetString() : element.ToString();
67+
}
68+
}

0 commit comments

Comments
 (0)