Skip to content
Merged
48 changes: 46 additions & 2 deletions src/Azure.DataApiBuilder.Mcp/Core/McpProtocolDefaults.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Globalization;
using Azure.DataApiBuilder.Product;
using Microsoft.Extensions.Configuration;

Expand All @@ -19,13 +20,18 @@ public static class McpProtocolDefaults
/// <summary>
/// Default MCP protocol version advertised when no configuration override is provided.
/// </summary>
public const string DEFAULT_PROTOCOL_VERSION = "2025-06-18";
public const string DEFAULT_PROTOCOL_VERSION = "2025-11-25";

/// <summary>
/// Configuration key used to override the MCP protocol version.
/// </summary>
public const string PROTOCOL_VERSION_CONFIG_KEY = "MCP:ProtocolVersion";

/// <summary>
/// Protocol version where MCP initialize server description is expected under serverInfo.description.
/// </summary>
public const string SERVER_INFO_DESCRIPTION_PROTOCOL_VERSION = "2025-11-25";

/// <summary>
/// Helper to resolve the effective protocol version from configuration.
/// Falls back to <see cref="DEFAULT_PROTOCOL_VERSION"/> when the key is not set.
Expand All @@ -34,6 +40,44 @@ public static string ResolveProtocolVersion(IConfiguration? configuration)
{
return configuration?.GetValue<string>(PROTOCOL_VERSION_CONFIG_KEY) ?? DEFAULT_PROTOCOL_VERSION;
}

/// <summary>
/// Resolves the protocol version to send in initialize response as the
/// greatest version that does not exceed the client requested version.
/// </summary>
/// <param name="supportedProtocolVersion">The server's effective supported protocol version.</param>
/// <param name="clientRequestedProtocolVersion">The protocol version requested by the client.</param>
/// <returns>The protocol version to return to the client.</returns>
public static string ResolveInitializeResponseProtocolVersion(string supportedProtocolVersion, string? clientRequestedProtocolVersion)
{
if (string.IsNullOrWhiteSpace(clientRequestedProtocolVersion))
{
return supportedProtocolVersion;
}

return CompareProtocolVersions(supportedProtocolVersion, clientRequestedProtocolVersion) <= 0
? supportedProtocolVersion
: clientRequestedProtocolVersion;
}

/// <summary>
/// Indicates whether initialize response metadata should use serverInfo.description instead of top-level instructions.
/// </summary>
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";
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);
}

return string.Compare(leftVersion, rightVersion, StringComparison.Ordinal);
}
}
}

65 changes: 53 additions & 12 deletions src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public async Task RunAsync(CancellationToken cancellationToken)
switch (method)
{
case "initialize":
HandleInitialize(id);
HandleInitialize(id, root);
break;

case "notifications/initialized":
Expand Down Expand Up @@ -167,22 +167,27 @@ public async Task RunAsync(CancellationToken cancellationToken)
/// <param name="id">
/// The request identifier extracted from the incoming JSON-RPC request. Used to correlate the response with the request.
/// </param>
/// <param name="root">The incoming initialize request payload.</param>
/// <remarks>
/// This method constructs and writes the MCP "initialize" response to STDOUT. It uses the protocol version defined by <c>PROTOCOL_VERSION</c>
/// 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.
/// </remarks>
private void HandleInitialize(JsonElement? id)
private void HandleInitialize(JsonElement? id, JsonElement root)
{
string? clientRequestedProtocolVersion = GetClientProtocolVersion(root);
string negotiatedProtocolVersion =
McpProtocolDefaults.ResolveInitializeResponseProtocolVersion(_protocolVersion, clientRequestedProtocolVersion);

// Get the description from runtime config if available
string? instructions = null;
string? description = null;
RuntimeConfigProvider? runtimeConfigProvider = _serviceProvider.GetService<RuntimeConfigProvider>();
if (runtimeConfigProvider != null)
{
try
{
RuntimeConfig runtimeConfig = runtimeConfigProvider.GetConfig();
instructions = runtimeConfig.Runtime?.Mcp?.Description;
description = runtimeConfig.Runtime?.Mcp?.Description;
}
catch (Exception)
{
Expand All @@ -191,13 +196,33 @@ private void HandleInitialize(JsonElement? id)
}
Comment thread
Aniruddh25 marked this conversation as resolved.
}

// 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 = _protocolVersion,
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
{
protocolVersion = negotiatedProtocolVersion,
capabilities = new
{
tools = new { listChanged = true },
Expand All @@ -208,14 +233,14 @@ private void HandleInitialize(JsonElement? id)
name = McpProtocolDefaults.MCP_SERVER_NAME,
version = McpProtocolDefaults.MCP_SERVER_VERSION
},
instructions = instructions
instructions = description
};
}
else
{
result = new
{
protocolVersion = _protocolVersion,
protocolVersion = negotiatedProtocolVersion,
capabilities = new
{
tools = new { listChanged = true },
Expand All @@ -232,6 +257,22 @@ private void HandleInitialize(JsonElement? id)
WriteResult(id, result);
}

private static string? GetClientProtocolVersion(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();
}

/// <summary>
/// Handles the "tools/list" JSON-RPC method by sending the list of available tools to the client.
/// </summary>
Expand Down
54 changes: 54 additions & 0 deletions src/Service.Tests/UnitTests/McpProtocolDefaultsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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
Comment thread
Aniruddh25 marked this conversation as resolved.
{
[TestMethod]
public void ResolveProtocolVersion_WithoutOverride_UsesLatestDefault()
{
IConfiguration config = new ConfigurationBuilder().Build();

string resolved = McpProtocolDefaults.ResolveProtocolVersion(config);

Assert.AreEqual("2025-11-25", resolved);
}

[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,
clientRequestedProtocolVersion);

Assert.AreEqual(expectedVersion, 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"));
}
}
}
Loading