From 495609f8f5a4f4fa17f13eb3e1af940f21cd2c51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 23:15:58 +0000 Subject: [PATCH 1/6] Initial plan From 56b7101849c1c58b5f2e5fb360ee117ab940ba76 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 23:21:47 +0000 Subject: [PATCH 2/6] Negotiate MCP initialize protocol version with client request Agent-Logs-Url: https://github.com/Azure/data-api-builder/sessions/7e83ced0-915a-414d-8d89-2533684eb764 Co-authored-by: Aniruddh25 <3513779+Aniruddh25@users.noreply.github.com> --- .../Core/McpProtocolDefaults.cs | 35 +++++++++++- .../Core/McpStdioServer.cs | 35 +++++++++--- .../UnitTests/McpProtocolDefaultsTests.cs | 53 +++++++++++++++++++ 3 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs index 48b235f480..60e4a1bc3a 100644 --- a/src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs +++ b/src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Azure.DataApiBuilder.Product; using Microsoft.Extensions.Configuration; @@ -19,7 +20,7 @@ public static class McpProtocolDefaults /// /// Default MCP protocol version advertised when no configuration override is provided. /// - public const string DEFAULT_PROTOCOL_VERSION = "2025-06-18"; + public const string DEFAULT_PROTOCOL_VERSION = "2025-11-25"; /// /// Configuration key used to override the MCP protocol version. @@ -34,6 +35,36 @@ public static string ResolveProtocolVersion(IConfiguration? configuration) { return configuration?.GetValue(PROTOCOL_VERSION_CONFIG_KEY) ?? DEFAULT_PROTOCOL_VERSION; } + + /// + /// Resolves the protocol version to send in initialize response as the + /// greatest version that does not exceed the client requested version. + /// + /// The server's effective supported protocol version. + /// The protocol version requested by the client. + /// The protocol version to return to the client. + public static string ResolveInitializeResponseProtocolVersion(string supportedProtocolVersion, string? clientRequestedProtocolVersion) + { + if (string.IsNullOrWhiteSpace(clientRequestedProtocolVersion)) + { + return supportedProtocolVersion; + } + + return CompareProtocolVersions(supportedProtocolVersion, clientRequestedProtocolVersion) <= 0 + ? supportedProtocolVersion + : clientRequestedProtocolVersion; + } + + private static int CompareProtocolVersions(string leftVersion, string rightVersion) + { + const string FORMAT = "yyyy-MM-dd"; + if (DateOnly.TryParseExact(leftVersion, FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly leftDate) && + DateOnly.TryParseExact(rightVersion, FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly rightDate)) + { + return leftDate.CompareTo(rightDate); + } + + return string.Compare(leftVersion, rightVersion, StringComparison.Ordinal); + } } } - diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs index 6736c7d57d..d8d5c61dc0 100644 --- a/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs +++ b/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs @@ -115,7 +115,7 @@ public async Task RunAsync(CancellationToken cancellationToken) switch (method) { case "initialize": - HandleInitialize(id); + HandleInitialize(id, root); break; case "notifications/initialized": @@ -160,13 +160,18 @@ public async Task RunAsync(CancellationToken cancellationToken) /// /// The request identifier extracted from the incoming JSON-RPC request. Used to correlate the response with the request. /// + /// The incoming initialize request payload. /// - /// This method constructs and writes the MCP "initialize" response to STDOUT. It uses the protocol version defined by PROTOCOL_VERSION - /// and includes supported capabilities and server information. No notifications are sent here; the server waits for the client to send - /// "notifications/initialized" before sending any notifications. + /// This method constructs and writes the MCP "initialize" response to STDOUT. It negotiates the response protocol version from the + /// server-supported version and client-requested version, and includes supported capabilities and server information. No notifications + /// are sent here; the server waits for the client to send "notifications/initialized" before sending any notifications. /// - private void HandleInitialize(JsonElement? id) + private void HandleInitialize(JsonElement? id, JsonElement root) { + string? clientRequestedProtocolVersion = TryGetClientProtocolVersion(root); + string negotiatedProtocolVersion = + McpProtocolDefaults.ResolveInitializeResponseProtocolVersion(_protocolVersion, clientRequestedProtocolVersion); + // Get the description from runtime config if available string? instructions = null; RuntimeConfigProvider? runtimeConfigProvider = _serviceProvider.GetService(); @@ -190,7 +195,7 @@ private void HandleInitialize(JsonElement? id) { result = new { - protocolVersion = _protocolVersion, + protocolVersion = negotiatedProtocolVersion, capabilities = new { tools = new { listChanged = true }, @@ -208,7 +213,7 @@ private void HandleInitialize(JsonElement? id) { result = new { - protocolVersion = _protocolVersion, + protocolVersion = negotiatedProtocolVersion, capabilities = new { tools = new { listChanged = true }, @@ -225,6 +230,22 @@ private void HandleInitialize(JsonElement? id) WriteResult(id, result); } + private static string? TryGetClientProtocolVersion(JsonElement root) + { + if (!root.TryGetProperty("params", out JsonElement paramsElement) || paramsElement.ValueKind != JsonValueKind.Object) + { + return null; + } + + if (!paramsElement.TryGetProperty("protocolVersion", out JsonElement protocolVersionElement) || + protocolVersionElement.ValueKind != JsonValueKind.String) + { + return null; + } + + return protocolVersionElement.GetString(); + } + /// /// Handles the "tools/list" JSON-RPC method by sending the list of available tools to the client. /// diff --git a/src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs b/src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs new file mode 100644 index 0000000000..1e01f2b2e0 --- /dev/null +++ b/src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Azure.DataApiBuilder.Mcp.Core; +using Microsoft.Extensions.Configuration; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Azure.DataApiBuilder.Service.Tests.UnitTests +{ + [TestClass] + public class McpProtocolDefaultsTests + { + [TestMethod] + public void ResolveProtocolVersion_WithoutOverride_UsesLatestDefault() + { + IConfiguration config = new ConfigurationBuilder().Build(); + + string resolved = McpProtocolDefaults.ResolveProtocolVersion(config); + + Assert.AreEqual("2025-11-25", resolved); + } + + [TestMethod] + public void ResolveInitializeResponseProtocolVersion_ClientRequestsNewerVersion_ReturnsSupportedVersion() + { + string resolved = McpProtocolDefaults.ResolveInitializeResponseProtocolVersion( + supportedProtocolVersion: "2025-11-25", + clientRequestedProtocolVersion: "2026-01-01"); + + Assert.AreEqual("2025-11-25", resolved); + } + + [TestMethod] + public void ResolveInitializeResponseProtocolVersion_ClientRequestsOlderVersion_ReturnsClientVersion() + { + string resolved = McpProtocolDefaults.ResolveInitializeResponseProtocolVersion( + supportedProtocolVersion: "2025-11-25", + clientRequestedProtocolVersion: "2025-06-18"); + + Assert.AreEqual("2025-06-18", resolved); + } + + [TestMethod] + public void ResolveInitializeResponseProtocolVersion_WithoutClientVersion_ReturnsSupportedVersion() + { + string resolved = McpProtocolDefaults.ResolveInitializeResponseProtocolVersion( + supportedProtocolVersion: "2025-11-25", + clientRequestedProtocolVersion: null); + + Assert.AreEqual("2025-11-25", resolved); + } + } +} From 86564d1c5dda6178624508a0794078c948fbb93b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 23:25:48 +0000 Subject: [PATCH 3/6] Address MCP protocol negotiation review feedback Agent-Logs-Url: https://github.com/Azure/data-api-builder/sessions/7e83ced0-915a-414d-8d89-2533684eb764 Co-authored-by: Aniruddh25 <3513779+Aniruddh25@users.noreply.github.com> --- .../Core/McpProtocolDefaults.cs | 6 +++--- src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs | 4 ++-- .../UnitTests/McpProtocolDefaultsTests.cs | 12 +++++++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs index 60e4a1bc3a..0cfcb5b424 100644 --- a/src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs +++ b/src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs @@ -57,9 +57,9 @@ public static string ResolveInitializeResponseProtocolVersion(string supportedPr private static int CompareProtocolVersions(string leftVersion, string rightVersion) { - const string FORMAT = "yyyy-MM-dd"; - if (DateOnly.TryParseExact(leftVersion, FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly leftDate) && - DateOnly.TryParseExact(rightVersion, FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly rightDate)) + const string PROTOCOL_VERSION_DATE_FORMAT = "yyyy-MM-dd"; + if (DateOnly.TryParseExact(leftVersion, PROTOCOL_VERSION_DATE_FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly leftDate) && + DateOnly.TryParseExact(rightVersion, PROTOCOL_VERSION_DATE_FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly rightDate)) { return leftDate.CompareTo(rightDate); } diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs index d8d5c61dc0..7165bf511d 100644 --- a/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs +++ b/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs @@ -168,7 +168,7 @@ public async Task RunAsync(CancellationToken cancellationToken) /// private void HandleInitialize(JsonElement? id, JsonElement root) { - string? clientRequestedProtocolVersion = TryGetClientProtocolVersion(root); + string? clientRequestedProtocolVersion = GetClientProtocolVersion(root); string negotiatedProtocolVersion = McpProtocolDefaults.ResolveInitializeResponseProtocolVersion(_protocolVersion, clientRequestedProtocolVersion); @@ -230,7 +230,7 @@ private void HandleInitialize(JsonElement? id, JsonElement root) WriteResult(id, result); } - private static string? TryGetClientProtocolVersion(JsonElement root) + private static string? GetClientProtocolVersion(JsonElement root) { if (!root.TryGetProperty("params", out JsonElement paramsElement) || paramsElement.ValueKind != JsonValueKind.Object) { diff --git a/src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs b/src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs index 1e01f2b2e0..c0b4dbb118 100644 --- a/src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs +++ b/src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs @@ -21,7 +21,7 @@ public void ResolveProtocolVersion_WithoutOverride_UsesLatestDefault() } [TestMethod] - public void ResolveInitializeResponseProtocolVersion_ClientRequestsNewerVersion_ReturnsSupportedVersion() + public void ResolveInitializeResponseProtocolVersion_ClientRequestsNewerVersion_ReturnsServerSupportedVersion() { string resolved = McpProtocolDefaults.ResolveInitializeResponseProtocolVersion( supportedProtocolVersion: "2025-11-25", @@ -49,5 +49,15 @@ public void ResolveInitializeResponseProtocolVersion_WithoutClientVersion_Return Assert.AreEqual("2025-11-25", resolved); } + + [TestMethod] + public void ResolveInitializeResponseProtocolVersion_NonDateVersionFormat_UsesOrdinalFallbackComparison() + { + string resolved = McpProtocolDefaults.ResolveInitializeResponseProtocolVersion( + supportedProtocolVersion: "a-version", + clientRequestedProtocolVersion: "z-version"); + + Assert.AreEqual("a-version", resolved); + } } } From 474557b51db93524a2cb288d3a8b98923444c63e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 00:08:11 +0000 Subject: [PATCH 4/6] Use serverInfo.description for MCP 2025-11-25 initialize metadata Agent-Logs-Url: https://github.com/Azure/data-api-builder/sessions/86017944-515e-443c-9f16-545350791623 Co-authored-by: Aniruddh25 <3513779+Aniruddh25@users.noreply.github.com> --- .../Core/McpProtocolDefaults.cs | 13 ++++++++ .../Core/McpStdioServer.cs | 30 +++++++++++++++---- .../UnitTests/McpProtocolDefaultsTests.cs | 13 ++++++++ 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs index 0cfcb5b424..289f6aa72d 100644 --- a/src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs +++ b/src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs @@ -27,6 +27,11 @@ public static class McpProtocolDefaults /// public const string PROTOCOL_VERSION_CONFIG_KEY = "MCP:ProtocolVersion"; + /// + /// Protocol version where MCP initialize server description is expected under serverInfo.description. + /// + public const string SERVER_INFO_DESCRIPTION_PROTOCOL_VERSION = "2025-11-25"; + /// /// Helper to resolve the effective protocol version from configuration. /// Falls back to when the key is not set. @@ -55,6 +60,14 @@ public static string ResolveInitializeResponseProtocolVersion(string supportedPr : clientRequestedProtocolVersion; } + /// + /// Indicates whether initialize response metadata should use serverInfo.description instead of top-level instructions. + /// + public static bool ShouldUseServerInfoDescription(string protocolVersion) + { + return CompareProtocolVersions(protocolVersion, SERVER_INFO_DESCRIPTION_PROTOCOL_VERSION) >= 0; + } + private static int CompareProtocolVersions(string leftVersion, string rightVersion) { const string PROTOCOL_VERSION_DATE_FORMAT = "yyyy-MM-dd"; diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs index 7165bf511d..835b5db662 100644 --- a/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs +++ b/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs @@ -173,14 +173,14 @@ private void HandleInitialize(JsonElement? id, JsonElement root) McpProtocolDefaults.ResolveInitializeResponseProtocolVersion(_protocolVersion, clientRequestedProtocolVersion); // Get the description from runtime config if available - string? instructions = null; + string? description = null; RuntimeConfigProvider? runtimeConfigProvider = _serviceProvider.GetService(); if (runtimeConfigProvider != null) { try { RuntimeConfig runtimeConfig = runtimeConfigProvider.GetConfig(); - instructions = runtimeConfig.Runtime?.Mcp?.Description; + description = runtimeConfig.Runtime?.Mcp?.Description; } catch (Exception) { @@ -189,9 +189,29 @@ private void HandleInitialize(JsonElement? id, JsonElement root) } } - // Create the initialize response - only include instructions if non-empty + bool shouldUseServerInfoDescription = McpProtocolDefaults.ShouldUseServerInfoDescription(negotiatedProtocolVersion); + + // Create the initialize response - only include description/instructions if non-empty object result; - if (!string.IsNullOrWhiteSpace(instructions)) + if (!string.IsNullOrWhiteSpace(description) && shouldUseServerInfoDescription) + { + result = new + { + protocolVersion = negotiatedProtocolVersion, + capabilities = new + { + tools = new { listChanged = true }, + logging = new { } + }, + serverInfo = new + { + name = McpProtocolDefaults.MCP_SERVER_NAME, + version = McpProtocolDefaults.MCP_SERVER_VERSION, + description = description + } + }; + } + else if (!string.IsNullOrWhiteSpace(description)) { result = new { @@ -206,7 +226,7 @@ private void HandleInitialize(JsonElement? id, JsonElement root) name = McpProtocolDefaults.MCP_SERVER_NAME, version = McpProtocolDefaults.MCP_SERVER_VERSION }, - instructions = instructions + instructions = description }; } else diff --git a/src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs b/src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs index c0b4dbb118..1846abb548 100644 --- a/src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs +++ b/src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs @@ -59,5 +59,18 @@ public void ResolveInitializeResponseProtocolVersion_NonDateVersionFormat_UsesOr Assert.AreEqual("a-version", resolved); } + + [TestMethod] + public void ShouldUseServerInfoDescription_AtOrAboveThreshold_ReturnsTrue() + { + Assert.IsTrue(McpProtocolDefaults.ShouldUseServerInfoDescription("2025-11-25")); + Assert.IsTrue(McpProtocolDefaults.ShouldUseServerInfoDescription("2025-12-01")); + } + + [TestMethod] + public void ShouldUseServerInfoDescription_BelowThreshold_ReturnsFalse() + { + Assert.IsFalse(McpProtocolDefaults.ShouldUseServerInfoDescription("2025-06-18")); + } } } From 73540aafdc779eb650e29e345c58b5e55373b61f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 20:02:28 +0000 Subject: [PATCH 5/6] Add HandleInitialize wire-shape tests for MCP initialize responses Agent-Logs-Url: https://github.com/Azure/data-api-builder/sessions/901e8c47-40c1-4349-82f3-727b5c85a884 Co-authored-by: aaronburtle <93220300+aaronburtle@users.noreply.github.com> --- .../McpStdioServerInitializeTests.cs | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 src/Service.Tests/UnitTests/McpStdioServerInitializeTests.cs diff --git a/src/Service.Tests/UnitTests/McpStdioServerInitializeTests.cs b/src/Service.Tests/UnitTests/McpStdioServerInitializeTests.cs new file mode 100644 index 0000000000..53cff20188 --- /dev/null +++ b/src/Service.Tests/UnitTests/McpStdioServerInitializeTests.cs @@ -0,0 +1,200 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Reflection; +using System.Text.Json; +using Azure.DataApiBuilder.Config; +using Azure.DataApiBuilder.Config.ObjectModel; +using Azure.DataApiBuilder.Core.Configurations; +using Azure.DataApiBuilder.Mcp.Core; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Azure.DataApiBuilder.Service.Tests.UnitTests +{ + [TestClass] + public class McpStdioServerInitializeTests + { + [TestMethod] + public void HandleInitialize_ClientRequests2025_11_25_WithDescription_UsesServerInfoDescription() + { + const string DESCRIPTION = "mcp description"; + McpStdioServer server = CreateServer(description: DESCRIPTION, out StringWriter stdoutCapture); + + JsonElement responseRoot = InvokeHandleInitialize( + server, + stdoutCapture, + """ + {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"client","version":"1.0.0"}}} + """); + + AssertInitializeEnvelopeAndCapabilities(responseRoot, expectedId: 1, expectedProtocolVersion: "2025-11-25"); + JsonElement result = responseRoot.GetProperty("result"); + + Assert.IsTrue(result.TryGetProperty("serverInfo", out JsonElement serverInfo), "Expected result.serverInfo."); + Assert.AreEqual(DESCRIPTION, serverInfo.GetProperty("description").GetString()); + Assert.IsFalse(result.TryGetProperty("instructions", out _), "Did not expect top-level instructions for 2025-11-25."); + Assert.AreEqual(1, CountOutputLines(stdoutCapture)); + } + + [TestMethod] + public void HandleInitialize_ClientRequests2025_06_18_WithDescription_UsesTopLevelInstructions() + { + const string DESCRIPTION = "legacy instruction text"; + McpStdioServer server = CreateServer(description: DESCRIPTION, out StringWriter stdoutCapture); + + JsonElement responseRoot = InvokeHandleInitialize( + server, + stdoutCapture, + """ + {"jsonrpc":"2.0","id":"abc","method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"client","version":"1.0.0"}}} + """); + + AssertInitializeEnvelopeAndCapabilities(responseRoot, expectedId: "abc", expectedProtocolVersion: "2025-06-18"); + JsonElement result = responseRoot.GetProperty("result"); + + Assert.AreEqual(DESCRIPTION, result.GetProperty("instructions").GetString()); + Assert.IsFalse(result.GetProperty("serverInfo").TryGetProperty("description", out _), "Did not expect serverInfo.description for 2025-06-18."); + Assert.AreEqual(1, CountOutputLines(stdoutCapture)); + } + + [TestMethod] + public void HandleInitialize_ClientRequests2025_11_25_WithoutDescription_EmitsNeitherField() + { + McpStdioServer server = CreateServer(description: null, out StringWriter stdoutCapture); + + JsonElement responseRoot = InvokeHandleInitialize( + server, + stdoutCapture, + """ + {"jsonrpc":"2.0","id":2,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"client","version":"1.0.0"}}} + """); + + AssertInitializeEnvelopeAndCapabilities(responseRoot, expectedId: 2, expectedProtocolVersion: "2025-11-25"); + JsonElement result = responseRoot.GetProperty("result"); + + Assert.IsFalse(result.TryGetProperty("instructions", out _), "Did not expect top-level instructions when description is not configured."); + Assert.IsFalse(result.GetProperty("serverInfo").TryGetProperty("description", out _), "Did not expect serverInfo.description when description is not configured."); + Assert.AreEqual(1, CountOutputLines(stdoutCapture)); + } + + private static McpStdioServer CreateServer(string? description, out StringWriter stdoutCapture) + { + stdoutCapture = new StringWriter(); + McpStdoutWriter stdoutWriter = new(stdoutCapture); + + RuntimeConfig runtimeConfig = new( + Schema: RuntimeConfig.DEFAULT_CONFIG_SCHEMA_LINK, + DataSource: null, + Entities: new RuntimeEntities(new Dictionary()), + Runtime: new RuntimeOptions( + Rest: null, + GraphQL: null, + Mcp: new McpRuntimeOptions(Description: description), + Host: null)); + RuntimeConfigProvider runtimeConfigProvider = new StubRuntimeConfigProvider(runtimeConfig); + + IConfiguration configuration = new ConfigurationBuilder().Build(); + ServiceProvider serviceProvider = new ServiceCollection() + .AddSingleton(configuration) + .AddSingleton(stdoutWriter) + .AddSingleton(runtimeConfigProvider) + .BuildServiceProvider(); + + return new McpStdioServer(new McpToolRegistry(), serviceProvider); + } + + private static JsonElement InvokeHandleInitialize(McpStdioServer server, StringWriter stdoutCapture, string initializeRequestJson) + { + MethodInfo? handleInitialize = typeof(McpStdioServer).GetMethod("HandleInitialize", BindingFlags.NonPublic | BindingFlags.Instance); + Assert.IsNotNull(handleInitialize, "Expected private HandleInitialize method to exist."); + + using JsonDocument request = JsonDocument.Parse(initializeRequestJson); + JsonElement requestRoot = request.RootElement; + JsonElement? id = requestRoot.TryGetProperty("id", out JsonElement idElement) ? idElement : null; + + handleInitialize.Invoke(server, new object?[] { id, requestRoot }); + + string output = ExtractSingleOutputLine(stdoutCapture); + using JsonDocument response = JsonDocument.Parse(output); + return response.RootElement.Clone(); + } + + private static void AssertInitializeEnvelopeAndCapabilities(JsonElement responseRoot, object expectedId, string expectedProtocolVersion) + { + Assert.AreEqual("2.0", responseRoot.GetProperty("jsonrpc").GetString()); + if (expectedId is int expectedNumericId) + { + Assert.AreEqual(expectedNumericId, responseRoot.GetProperty("id").GetInt32()); + } + else + { + Assert.AreEqual(expectedId, responseRoot.GetProperty("id").GetString()); + } + + JsonElement result = responseRoot.GetProperty("result"); + Assert.AreEqual(expectedProtocolVersion, result.GetProperty("protocolVersion").GetString()); + + JsonElement capabilities = result.GetProperty("capabilities"); + Assert.IsTrue(capabilities.GetProperty("tools").GetProperty("listChanged").GetBoolean()); + Assert.AreEqual(JsonValueKind.Object, capabilities.GetProperty("logging").ValueKind); + + JsonElement serverInfo = result.GetProperty("serverInfo"); + Assert.AreEqual(McpProtocolDefaults.MCP_SERVER_NAME, serverInfo.GetProperty("name").GetString()); + Assert.AreEqual(McpProtocolDefaults.MCP_SERVER_VERSION, serverInfo.GetProperty("version").GetString()); + } + + private static int CountOutputLines(StringWriter stdoutCapture) + { + return stdoutCapture + .ToString() + .Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries) + .Length; + } + + private static string ExtractSingleOutputLine(StringWriter stdoutCapture) + { + string[] lines = stdoutCapture + .ToString() + .Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); + Assert.AreEqual(1, lines.Length, "Expected a single JSON-RPC response line."); + return lines[0]; + } + + private sealed class StubRuntimeConfigProvider : RuntimeConfigProvider + { + private readonly RuntimeConfig _runtimeConfig; + + public StubRuntimeConfigProvider(RuntimeConfig runtimeConfig) : base(new StubRuntimeConfigLoader()) + { + _runtimeConfig = runtimeConfig; + } + + public override RuntimeConfig GetConfig() + { + return _runtimeConfig; + } + } + + private sealed class StubRuntimeConfigLoader : RuntimeConfigLoader + { + public override bool TryLoadKnownConfig([NotNullWhen(true)] out RuntimeConfig? config, bool replaceEnvVar = false) + { + config = null; + return false; + } + + public override string GetPublishedDraftSchemaLink() + { + return RuntimeConfig.DEFAULT_CONFIG_SCHEMA_LINK; + } + } + } +} From 59dca7adf66507cd50e5ecb4f4fecb999c565260 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 21:21:48 +0000 Subject: [PATCH 6/6] Refactor MCP protocol negotiation tests to DataRow and add same-version case --- .../UnitTests/McpProtocolDefaultsTests.cs | 48 +++++-------------- 1 file changed, 13 insertions(+), 35 deletions(-) diff --git a/src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs b/src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs index 1846abb548..341f17986a 100644 --- a/src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs +++ b/src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs @@ -20,44 +20,22 @@ public void ResolveProtocolVersion_WithoutOverride_UsesLatestDefault() Assert.AreEqual("2025-11-25", resolved); } - [TestMethod] - public void ResolveInitializeResponseProtocolVersion_ClientRequestsNewerVersion_ReturnsServerSupportedVersion() - { - string resolved = McpProtocolDefaults.ResolveInitializeResponseProtocolVersion( - supportedProtocolVersion: "2025-11-25", - clientRequestedProtocolVersion: "2026-01-01"); - - Assert.AreEqual("2025-11-25", resolved); - } - - [TestMethod] - public void ResolveInitializeResponseProtocolVersion_ClientRequestsOlderVersion_ReturnsClientVersion() - { - string resolved = McpProtocolDefaults.ResolveInitializeResponseProtocolVersion( - supportedProtocolVersion: "2025-11-25", - clientRequestedProtocolVersion: "2025-06-18"); - - Assert.AreEqual("2025-06-18", resolved); - } - - [TestMethod] - public void ResolveInitializeResponseProtocolVersion_WithoutClientVersion_ReturnsSupportedVersion() - { - string resolved = McpProtocolDefaults.ResolveInitializeResponseProtocolVersion( - supportedProtocolVersion: "2025-11-25", - clientRequestedProtocolVersion: null); - - Assert.AreEqual("2025-11-25", resolved); - } - - [TestMethod] - public void ResolveInitializeResponseProtocolVersion_NonDateVersionFormat_UsesOrdinalFallbackComparison() + [DataTestMethod] + [DataRow("2025-11-25", "2026-01-01", "2025-11-25")] + [DataRow("2025-11-25", "2025-06-18", "2025-06-18")] + [DataRow("2025-11-25", "2025-11-25", "2025-11-25")] + [DataRow("2025-11-25", null, "2025-11-25")] + [DataRow("a-version", "z-version", "a-version")] + public void ResolveInitializeResponseProtocolVersion_ReturnsExpectedNegotiatedVersion( + string supportedProtocolVersion, + string clientRequestedProtocolVersion, + string expectedVersion) { string resolved = McpProtocolDefaults.ResolveInitializeResponseProtocolVersion( - supportedProtocolVersion: "a-version", - clientRequestedProtocolVersion: "z-version"); + supportedProtocolVersion, + clientRequestedProtocolVersion); - Assert.AreEqual("a-version", resolved); + Assert.AreEqual(expectedVersion, resolved); } [TestMethod]