diff --git a/Directory.Packages.props b/Directory.Packages.props index a67fc2e7f4..8a64afd25b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -89,8 +89,8 @@ - - + + diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Client/MockClientTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Client/MockClientTests.cs index 9db986c6ea..58fd2d43d2 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Client/MockClientTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Client/MockClientTests.cs @@ -16,7 +16,9 @@ private static McpServerOptions CreateOptions(McpServerHandlers? serverHandlers { return new McpServerOptions { - ProtocolVersion = "2024", + // Use a supported protocol version. preview.3 tightened validation and + // rejects bare '2024'; supported versions: 2024-11-05, 2025-11-25, 2026-07-28. + ProtocolVersion = "2025-11-25", InitializationTimeout = TimeSpan.FromSeconds(30), Handlers = serverHandlers ?? new(), ServerInfo = new Implementation { Name = "Azure MCP", Version = "1.0.0-beta" } @@ -50,7 +52,7 @@ await Invoke_Request_To_Server( Assert.NotNull(result); Assert.Equal("Azure MCP", result.ServerInfo.Name); Assert.Equal("1.0.0-beta", result.ServerInfo.Version); - Assert.Equal("2024", result.ProtocolVersion); + Assert.Equal("2025-11-25", result.ProtocolVersion); }); } diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/ClientToolTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/ClientToolTests.cs index 4a0ac6835a..8958a5bb6e 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/ClientToolTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/ClientToolTests.cs @@ -112,7 +112,13 @@ public async Task Should_Error_When_Resources_Unsubscribe_Not_Supported() [Fact] public async Task Should_Not_Hang_On_Logging_SetLevel_Not_Supported() { - await Client.SetLoggingLevelAsync(LoggingLevel.Info, cancellationToken: TestContext.Current.CancellationToken); + // logging/setLevel was removed in MCP 2026-07-28 (SDK 2.0.0-preview.3). + // The method is no longer supported; per-request log level is now set via + // _meta/io.modelcontextprotocol/logLevel. The call should throw rather than hang. + var ex = await Assert.ThrowsAsync( + async () => await Client.SetLoggingLevelAsync(LoggingLevel.Info, + cancellationToken: TestContext.Current.CancellationToken)); + Assert.Contains("logging/setLevel", ex.Message, StringComparison.OrdinalIgnoreCase); } [Fact] diff --git a/servers/Azure.Mcp.Server/changelog-entries/1784845074408.yaml b/servers/Azure.Mcp.Server/changelog-entries/1784845074408.yaml new file mode 100644 index 0000000000..d071b34bf4 --- /dev/null +++ b/servers/Azure.Mcp.Server/changelog-entries/1784845074408.yaml @@ -0,0 +1,4 @@ +changes: + - section: "Other Changes" + description: "Updated ModelContextProtocol packages to 2.0.0-preview.3." + subsection: "Dependency Updates" diff --git a/servers/Azure.Mcp.Server/tests/Azure.Mcp.Server.Tests/Infrastructure/ConsolidatedModeTests.cs b/servers/Azure.Mcp.Server/tests/Azure.Mcp.Server.Tests/Infrastructure/ConsolidatedModeTests.cs index 5ae800e00d..e78ab176f1 100644 --- a/servers/Azure.Mcp.Server/tests/Azure.Mcp.Server.Tests/Infrastructure/ConsolidatedModeTests.cs +++ b/servers/Azure.Mcp.Server/tests/Azure.Mcp.Server.Tests/Infrastructure/ConsolidatedModeTests.cs @@ -2,12 +2,51 @@ // Licensed under the MIT License. using System.Net; +using System.Text.Json.Nodes; using Xunit; namespace Azure.Mcp.Server.Tests.Infrastructure; public class ConsolidatedModeTests { + private const string StatelessProtocolVersion = "2026-07-28"; + + /// + /// Builds a for a 2026-07-28 stateless JSON-RPC request + /// containing the required _meta envelope fields. Optional extra _meta + /// entries (e.g. W3C trace context) can be merged in via . + /// + private static StringContent CreateStateless2026RequestContent( + string method, + int id = 1, + JsonObject? extraMeta = null) + { + var meta = new JsonObject + { + ["io.modelcontextprotocol/protocolVersion"] = StatelessProtocolVersion, + ["io.modelcontextprotocol/clientInfo"] = new JsonObject { ["name"] = "test-client", ["version"] = "1.0" }, + ["io.modelcontextprotocol/clientCapabilities"] = new JsonObject() + }; + + if (extraMeta != null) + { + foreach (var (key, value) in extraMeta) + { + meta[key] = value?.DeepClone(); + } + } + + var body = new JsonObject + { + ["jsonrpc"] = "2.0", + ["id"] = id, + ["method"] = method, + ["params"] = new JsonObject { ["_meta"] = meta } + }; + + return new StringContent(body.ToJsonString(), System.Text.Encoding.UTF8, "application/json"); + } + [Fact] public async Task ConsolidatedMode_HttpTransport_Should_List_Tools_On_Root_Endpoint() { @@ -37,10 +76,10 @@ public async Task ConsolidatedMode_HttpTransport_Should_List_Tools_On_Root_Endpo using var client = new HttpClient(); using var request = new HttpRequestMessage(HttpMethod.Post, $"http://127.0.0.1:{port}/") { - Content = new StringContent("{" + "\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}" + "}", System.Text.Encoding.UTF8, "application/json") + Content = CreateStateless2026RequestContent("tools/list") }; request.Headers.TryAddWithoutValidation("Accept", "application/json, text/event-stream"); - request.Headers.TryAddWithoutValidation("MCP-Protocol-Version", "2026-07-28"); + request.Headers.TryAddWithoutValidation("MCP-Protocol-Version", StatelessProtocolVersion); request.Headers.TryAddWithoutValidation("Mcp-Method", "tools/list"); request.Headers.TryAddWithoutValidation("Mcp-Name", "tools/list"); @@ -93,16 +132,17 @@ public async Task ConsolidatedMode_HttpTransport_Should_Accept_Meta_In_Request_P using var client = new HttpClient(); using var request = new HttpRequestMessage(HttpMethod.Post, $"http://127.0.0.1:{port}/") { - Content = new StringContent( - "{" + - "\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{\"_meta\":{\"traceparent\":\"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01\",\"clientId\":\"phase2-hardening-test\"}}" + - "}", - System.Text.Encoding.UTF8, - "application/json") + Content = CreateStateless2026RequestContent( + "tools/list", + extraMeta: new JsonObject + { + ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", + ["clientId"] = "phase2-hardening-test" + }) }; request.Headers.TryAddWithoutValidation("Accept", "application/json, text/event-stream"); - request.Headers.TryAddWithoutValidation("MCP-Protocol-Version", "2026-07-28"); + request.Headers.TryAddWithoutValidation("MCP-Protocol-Version", StatelessProtocolVersion); request.Headers.TryAddWithoutValidation("Mcp-Method", "tools/list"); request.Headers.TryAddWithoutValidation("Mcp-Name", "tools/list"); @@ -152,16 +192,11 @@ public async Task ConsolidatedMode_HttpTransport_Should_Support_ServerDiscover_W using var client = new HttpClient(); using var request = new HttpRequestMessage(HttpMethod.Post, $"http://127.0.0.1:{port}/") { - Content = new StringContent( - "{" + - "\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"server/discover\",\"params\":{}" + - "}", - System.Text.Encoding.UTF8, - "application/json") + Content = CreateStateless2026RequestContent("server/discover") }; request.Headers.TryAddWithoutValidation("Accept", "application/json, text/event-stream"); - request.Headers.TryAddWithoutValidation("MCP-Protocol-Version", "2026-07-28"); + request.Headers.TryAddWithoutValidation("MCP-Protocol-Version", StatelessProtocolVersion); request.Headers.TryAddWithoutValidation("Mcp-Method", "server/discover"); request.Headers.TryAddWithoutValidation("Mcp-Name", "server/discover"); @@ -213,10 +248,10 @@ public async Task ConsolidatedMode_HttpTransport_Should_Handle_Repeated_ToolsLis using var request1 = new HttpRequestMessage(HttpMethod.Post, $"http://127.0.0.1:{port}/") { - Content = new StringContent("{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}}", System.Text.Encoding.UTF8, "application/json") + Content = CreateStateless2026RequestContent("tools/list", id: 1) }; request1.Headers.TryAddWithoutValidation("Accept", "application/json, text/event-stream"); - request1.Headers.TryAddWithoutValidation("MCP-Protocol-Version", "2026-07-28"); + request1.Headers.TryAddWithoutValidation("MCP-Protocol-Version", StatelessProtocolVersion); request1.Headers.TryAddWithoutValidation("Mcp-Method", "tools/list"); request1.Headers.TryAddWithoutValidation("Mcp-Name", "tools/list"); @@ -225,10 +260,10 @@ public async Task ConsolidatedMode_HttpTransport_Should_Handle_Repeated_ToolsLis using var request2 = new HttpRequestMessage(HttpMethod.Post, $"http://127.0.0.1:{port}/") { - Content = new StringContent("{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\",\"params\":{}}", System.Text.Encoding.UTF8, "application/json") + Content = CreateStateless2026RequestContent("tools/list", id: 2) }; request2.Headers.TryAddWithoutValidation("Accept", "application/json, text/event-stream"); - request2.Headers.TryAddWithoutValidation("MCP-Protocol-Version", "2026-07-28"); + request2.Headers.TryAddWithoutValidation("MCP-Protocol-Version", StatelessProtocolVersion); request2.Headers.TryAddWithoutValidation("Mcp-Method", "tools/list"); request2.Headers.TryAddWithoutValidation("Mcp-Name", "tools/list"); @@ -341,22 +376,18 @@ public async Task ConsolidatedMode_HttpTransport_Should_Propagate_W3C_Trace_Cont using var client = new HttpClient(); using var request = new HttpRequestMessage(HttpMethod.Post, $"http://127.0.0.1:{port}/") { - Content = new StringContent( - "{" + - "\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{" + - "\"_meta\":{" + - "\"traceparent\":\"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01\"," + - "\"tracestate\":\"vendor1=opaquevalue1,vendor2=opaquevalue2\"," + - "\"baggage\":\"userId=alice,serverNode=DF:28,isProduction=false\"" + - "}" + - "}" + - "}", - System.Text.Encoding.UTF8, - "application/json") + Content = CreateStateless2026RequestContent( + "tools/list", + extraMeta: new JsonObject + { + ["traceparent"] = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", + ["tracestate"] = "vendor1=opaquevalue1,vendor2=opaquevalue2", + ["baggage"] = "userId=alice,serverNode=DF:28,isProduction=false" + }) }; request.Headers.TryAddWithoutValidation("Accept", "application/json, text/event-stream"); - request.Headers.TryAddWithoutValidation("MCP-Protocol-Version", "2026-07-28"); + request.Headers.TryAddWithoutValidation("MCP-Protocol-Version", StatelessProtocolVersion); request.Headers.TryAddWithoutValidation("Mcp-Method", "tools/list"); request.Headers.TryAddWithoutValidation("Mcp-Name", "tools/list"); @@ -634,10 +665,10 @@ public async Task ConsolidatedMode_HttpTransport_Should_Accept_NonNamedMethod_Wi using var client = new HttpClient(); using var request = new HttpRequestMessage(HttpMethod.Post, $"http://127.0.0.1:{port}/") { - Content = new StringContent("{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}}", System.Text.Encoding.UTF8, "application/json") + Content = CreateStateless2026RequestContent("tools/list") }; request.Headers.TryAddWithoutValidation("Accept", "application/json, text/event-stream"); - request.Headers.TryAddWithoutValidation("MCP-Protocol-Version", "2026-07-28"); + request.Headers.TryAddWithoutValidation("MCP-Protocol-Version", StatelessProtocolVersion); request.Headers.TryAddWithoutValidation("Mcp-Method", "tools/list"); // Intentionally omit Mcp-Name — valid for a non-named method diff --git a/servers/Azure.Mcp.Server/tests/Azure.Mcp.Server.Tests/Infrastructure/ServerModeCoverageTests.cs b/servers/Azure.Mcp.Server/tests/Azure.Mcp.Server.Tests/Infrastructure/ServerModeCoverageTests.cs index 61caed58aa..1aa98a4fb9 100644 --- a/servers/Azure.Mcp.Server/tests/Azure.Mcp.Server.Tests/Infrastructure/ServerModeCoverageTests.cs +++ b/servers/Azure.Mcp.Server/tests/Azure.Mcp.Server.Tests/Infrastructure/ServerModeCoverageTests.cs @@ -115,6 +115,7 @@ public async Task NamespaceMode_Should_List_Tools_Without_Initialize() public async Task AllMode_Should_List_Tools_Without_Initialize() { // "all" mode exposes every individual Azure tool directly. + // Uses a longer timeout because registering all tools takes more time on slow CI runners. Assert.True(File.Exists(AzmcpPath), $"Executable not found at {AzmcpPath}. Please build the Azure.Mcp.Server project first."); var processStartInfo = new System.Diagnostics.ProcessStartInfo @@ -141,7 +142,8 @@ public async Task AllMode_Should_List_Tools_Without_Initialize() await process.StandardInput.WriteLineAsync(listToolsRequest); await process.StandardInput.FlushAsync(TestContext.Current.CancellationToken); - var response = await ReadResponseAsync(process.StandardOutput); + // Allow up to 60 s: "all" mode registers every tool and is slower on macOS CI runners. + var response = await ReadResponseAsync(process.StandardOutput, TimeSpan.FromSeconds(60)); Assert.NotNull(response); Assert.Contains("\"result\"", response, StringComparison.OrdinalIgnoreCase); @@ -156,9 +158,9 @@ public async Task AllMode_Should_List_Tools_Without_Initialize() } } - private static async Task ReadResponseAsync(StreamReader reader) + private static async Task ReadResponseAsync(StreamReader reader, TimeSpan? timeout = null) { - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15)); + using var cts = new CancellationTokenSource(timeout ?? TimeSpan.FromSeconds(15)); try { return await reader.ReadLineAsync(cts.Token); diff --git a/servers/Fabric.Mcp.Server/tests/Fabric.Mcp.Server.Tests/Infrastructure/ServerStartupTests.cs b/servers/Fabric.Mcp.Server/tests/Fabric.Mcp.Server.Tests/Infrastructure/ServerStartupTests.cs index e668c23424..8be8292c54 100644 --- a/servers/Fabric.Mcp.Server/tests/Fabric.Mcp.Server.Tests/Infrastructure/ServerStartupTests.cs +++ b/servers/Fabric.Mcp.Server/tests/Fabric.Mcp.Server.Tests/Infrastructure/ServerStartupTests.cs @@ -2,12 +2,39 @@ // Licensed under the MIT License. using System.Net; +using System.Text.Json.Nodes; using Xunit; namespace Fabric.Mcp.Server.Tests.Infrastructure; public class ServerStartupTests { + private const string StatelessProtocolVersion = "2026-07-28"; + + /// + /// Builds a for a 2026-07-28 stateless JSON-RPC request + /// containing the required _meta envelope fields. + /// + private static StringContent CreateStateless2026RequestContent(string method, int id = 1) + { + var body = new JsonObject + { + ["jsonrpc"] = "2.0", + ["id"] = id, + ["method"] = method, + ["params"] = new JsonObject + { + ["_meta"] = new JsonObject + { + ["io.modelcontextprotocol/protocolVersion"] = StatelessProtocolVersion, + ["io.modelcontextprotocol/clientInfo"] = new JsonObject { ["name"] = "test-client", ["version"] = "1.0" }, + ["io.modelcontextprotocol/clientCapabilities"] = new JsonObject() + } + } + }; + return new StringContent(body.ToJsonString(), System.Text.Encoding.UTF8, "application/json"); + } + [Fact] public async Task Server_Should_List_Tools_Over_Http_Root_Endpoint() { @@ -47,10 +74,10 @@ public async Task Server_Should_List_Tools_Over_Http_Root_Endpoint() using var client = new HttpClient(); using var request = new HttpRequestMessage(HttpMethod.Post, $"http://127.0.0.1:{port}/") { - Content = new StringContent("{" + "\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}" + "}", System.Text.Encoding.UTF8, "application/json") + Content = CreateStateless2026RequestContent("tools/list") }; request.Headers.TryAddWithoutValidation("Accept", "application/json, text/event-stream"); - request.Headers.TryAddWithoutValidation("MCP-Protocol-Version", "2026-07-28"); + request.Headers.TryAddWithoutValidation("MCP-Protocol-Version", StatelessProtocolVersion); request.Headers.TryAddWithoutValidation("Mcp-Method", "tools/list"); request.Headers.TryAddWithoutValidation("Mcp-Name", "tools/list");