Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System.Text.Json;
using Azure.DataApiBuilder.Config.ObjectModel;
using Azure.DataApiBuilder.Mcp.Model;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol;
Expand All @@ -21,7 +22,8 @@ internal static IServiceCollection ConfigureMcpServer(this IServiceCollection se
{
services.AddMcpServer(options =>
{
options.ServerInfo = new() { Name = "Data API builder MCP Server", Version = "1.0.0" };
options.ServerInfo = new() { Name = "SQL MCP Server", Version = "1.0.0" };

options.Capabilities = new()
{
Tools = new()
Expand Down
53 changes: 38 additions & 15 deletions src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using Azure.DataApiBuilder.Config.ObjectModel;
using Azure.DataApiBuilder.Core.AuthenticationHelpers.AuthenticationSimulator;
using Azure.DataApiBuilder.Core.Configurations;
using Azure.DataApiBuilder.Mcp.Model;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
Expand Down Expand Up @@ -161,25 +163,46 @@ private void HandleInitialize(JsonElement? id)
// Extract the actual id value from the request
object? requestId = id.HasValue ? GetIdValue(id.Value) : null;

// Get the description from runtime config if available
string? instructions = null;
RuntimeConfigProvider? runtimeConfigProvider = _serviceProvider.GetService<RuntimeConfigProvider>();
if (runtimeConfigProvider != null)
{
try
{
RuntimeConfig runtimeConfig = runtimeConfigProvider.GetConfig();
instructions = runtimeConfig.Runtime?.Mcp?.Description;
}
catch (Exception ex)
{
// Log to stderr for diagnostics and rethrow to avoid masking configuration errors
Console.Error.WriteLine($"[MCP WARNING] Failed to retrieve MCP description from config: {ex.Message}");
throw;
}
}

// Create the initialize response
var response = new
object result = new
{
protocolVersion = _protocolVersion,
capabilities = new
{
tools = new { listChanged = true },
logging = new { }
},
serverInfo = new
{
name = "SQL MCP Server",
version = "1.0.0"
},
instructions = !string.IsNullOrWhiteSpace(instructions) ? instructions : null
};

object response = new
{
jsonrpc = "2.0",
id = requestId,
result = new
{
protocolVersion = _protocolVersion,
capabilities = new
{
tools = new { listChanged = true },
logging = new { }
},
serverInfo = new
{
name = "Data API Builder",
version = "1.0.0"
}
}
result
};

string json = JsonSerializer.Serialize(response);
Expand Down
28 changes: 28 additions & 0 deletions src/Cli.Tests/ConfigureOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,34 @@ public void TestFailureWhenAddingSetSessionContextToMySQLDatabase()
Assert.IsFalse(isSuccess);
}

/// <summary>
/// Tests that running "dab configure --runtime.mcp.description {value}" on a config with various values results
/// in runtime config update. Takes in updated value for mcp.description and
/// validates whether the runtime config reflects those updated values
/// </summary>
[DataTestMethod]
[DataRow("This MCP provides access to the Products database and should be used to answer product-related or inventory-related questions from the user.", DisplayName = "Set MCP description.")]
[DataRow("Use this server for customer data queries.", DisplayName = "Set MCP description with short text.")]
public void TestConfigureDescriptionForMcpSettings(string descriptionValue)
{
// Arrange -> all the setup which includes creating options.
SetupFileSystemWithInitialConfig(INITIAL_CONFIG);

// Act: Attempts to update mcp.description value
ConfigureOptions options = new(
runtimeMcpDescription: descriptionValue,
config: TEST_RUNTIME_CONFIG_FILE
);
bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!);

// Assert: Validate the Description is updated
Assert.IsTrue(isSuccess);
string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE);
Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(updatedConfig, out RuntimeConfig? runtimeConfig));
Assert.IsNotNull(runtimeConfig.Runtime?.Mcp?.Description);
Assert.AreEqual(descriptionValue, runtimeConfig.Runtime.Mcp.Description);
}

/// <summary>
/// Sets up the mock file system with an initial configuration file.
/// This method adds a config file to the mock file system and verifies its existence.
Expand Down
5 changes: 5 additions & 0 deletions src/Cli/Commands/ConfigureOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public ConfigureOptions(
bool? runtimeRestRequestBodyStrict = null,
bool? runtimeMcpEnabled = null,
string? runtimeMcpPath = null,
string? runtimeMcpDescription = null,
bool? runtimeMcpDmlToolsEnabled = null,
bool? runtimeMcpDmlToolsDescribeEntitiesEnabled = null,
bool? runtimeMcpDmlToolsCreateRecordEnabled = null,
Expand Down Expand Up @@ -93,6 +94,7 @@ public ConfigureOptions(
// Mcp
RuntimeMcpEnabled = runtimeMcpEnabled;
RuntimeMcpPath = runtimeMcpPath;
RuntimeMcpDescription = runtimeMcpDescription;
RuntimeMcpDmlToolsEnabled = runtimeMcpDmlToolsEnabled;
RuntimeMcpDmlToolsDescribeEntitiesEnabled = runtimeMcpDmlToolsDescribeEntitiesEnabled;
RuntimeMcpDmlToolsCreateRecordEnabled = runtimeMcpDmlToolsCreateRecordEnabled;
Expand Down Expand Up @@ -180,6 +182,9 @@ public ConfigureOptions(
[Option("runtime.mcp.path", Required = false, HelpText = "Customize DAB's MCP endpoint path. Default: '/mcp' Conditions: Prefix path with '/'.")]
public string? RuntimeMcpPath { get; }

[Option("runtime.mcp.description", Required = false, HelpText = "Set the MCP server description to be exposed in the initialize response.")]
public string? RuntimeMcpDescription { get; }

[Option("runtime.mcp.dml-tools.enabled", Required = false, HelpText = "Enable DAB's MCP DML tools endpoint. Default: true (boolean).")]
public bool? RuntimeMcpDmlToolsEnabled { get; }

Expand Down
18 changes: 17 additions & 1 deletion src/Cli/ConfigGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -810,7 +810,15 @@ private static bool TryUpdateConfiguredRuntimeOptions(

// MCP: Enabled and Path
if (options.RuntimeMcpEnabled != null ||
options.RuntimeMcpPath != null)
options.RuntimeMcpPath != null ||
options.RuntimeMcpDescription != null ||
options.RuntimeMcpDmlToolsEnabled != null ||
options.RuntimeMcpDmlToolsDescribeEntitiesEnabled != null ||
options.RuntimeMcpDmlToolsCreateRecordEnabled != null ||
options.RuntimeMcpDmlToolsReadRecordsEnabled != null ||
options.RuntimeMcpDmlToolsUpdateRecordEnabled != null ||
options.RuntimeMcpDmlToolsDeleteRecordEnabled != null ||
options.RuntimeMcpDmlToolsExecuteEntityEnabled != null)
{
McpRuntimeOptions updatedMcpOptions = runtimeConfig?.Runtime?.Mcp ?? new();
bool status = TryUpdateConfiguredMcpValues(options, ref updatedMcpOptions);
Expand Down Expand Up @@ -1066,6 +1074,14 @@ private static bool TryUpdateConfiguredMcpValues(
}
}

// Runtime.Mcp.Description
updatedValue = options?.RuntimeMcpDescription;
if (updatedValue != null)
{
updatedMcpOptions = updatedMcpOptions! with { Description = (string)updatedValue };
_logger.LogInformation("Updated RuntimeConfig with Runtime.Mcp.Description as '{updatedValue}'", updatedValue);
}

// Handle DML tools configuration
bool hasToolUpdates = false;
DmlToolsConfig? currentDmlTools = updatedMcpOptions?.DmlTools;
Expand Down
18 changes: 17 additions & 1 deletion src/Config/Converters/McpRuntimeOptionsConverterFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,13 @@ internal McpRuntimeOptionsConverter(DeserializationVariableReplacementSettings?
bool enabled = true;
string? path = null;
DmlToolsConfig? dmlTools = null;
string? description = null;

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return new McpRuntimeOptions(enabled, path, dmlTools);
return new McpRuntimeOptions(enabled, path, dmlTools, description);
}

string? propertyName = reader.GetString();
Expand Down Expand Up @@ -98,6 +99,14 @@ internal McpRuntimeOptionsConverter(DeserializationVariableReplacementSettings?
dmlTools = dmlToolsConfigConverter.Read(ref reader, typeToConvert, options);
break;

case "description":
if (reader.TokenType is not JsonTokenType.Null)
{
description = reader.DeserializeString(_replacementSettings);
}

break;

default:
throw new JsonException($"Unexpected property {propertyName}");
}
Expand Down Expand Up @@ -134,6 +143,13 @@ public override void Write(Utf8JsonWriter writer, McpRuntimeOptions value, JsonS
dmlToolsOptionsConverter.Write(writer, value.DmlTools, options);
}

// Write description if it's provided
if (value is not null && !string.IsNullOrWhiteSpace(value.Description))
{
writer.WritePropertyName("description");
JsonSerializer.Serialize(writer, value.Description, options);
}

writer.WriteEndObject();
}
}
Expand Down
11 changes: 10 additions & 1 deletion src/Config/ObjectModel/McpRuntimeOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,18 @@ public record McpRuntimeOptions
[JsonConverter(typeof(DmlToolsConfigConverter))]
public DmlToolsConfig? DmlTools { get; init; }

/// <summary>
/// Description of the MCP server to be exposed in the initialize response
/// </summary>
[JsonPropertyName("description")]
public string? Description { get; init; }

[JsonConstructor]
public McpRuntimeOptions(
bool? Enabled = null,
string? Path = null,
DmlToolsConfig? DmlTools = null)
DmlToolsConfig? DmlTools = null,
string? Description = null)
{
this.Enabled = Enabled ?? true;

Expand All @@ -58,6 +65,8 @@ public McpRuntimeOptions(
{
this.DmlTools = DmlTools;
}

this.Description = Description;
}

/// <summary>
Expand Down
Loading