Skip to content

Commit f1a0170

Browse files
CopilotJerryNixonCopilotAniruddh25anushakolan
authored
Add server-level description field to MCP runtime configuration (#3016)
## Why make this change? MCP clients and agents require high-level behavioral context for servers via the `initialize` response's `instructions` field. DAB previously had no mechanism to surface this configurable semantic guidance. ## What is this change? Added optional `description` field to MCP runtime configuration that populates the MCP protocol's `instructions` field: **Configuration model** - `McpRuntimeOptions` now accepts `description` parameter - `McpRuntimeOptionsConverter` handles serialization/deserialization **CLI integration** - `dab configure --runtime.mcp.description "text"` command support - Configuration generator validates and persists the value - Fixed config persistence bug: Added DML tools options to condition check to ensure MCP configuration updates are properly written to config file **MCP server response** - **Stdio Server**: `HandleInitialize()` retrieves description from `RuntimeConfig.Runtime.Mcp.Description` and conditionally includes `instructions` in initialize response when non-empty - **HTTP Server**: Updated server name to "SQL MCP Server" - Both servers now use explicit `object` type instead of `var` for better type clarity **Testing** - Added comprehensive unit tests in `McpRuntimeOptionsSerializationTests` covering: - Serialization/deserialization with description - Edge cases: null, empty strings, whitespace, very long strings (5000+ characters) - Special characters: quotes, newlines, tabs, unicode characters - Backward compatibility with existing configurations without description field - Improved assertion order to validate JSON field presence before value matching - Consolidated CLI tests: removed duplicate `TestAddDescriptionToMcpSettings` and renamed `TestUpdateDescriptionForMcpSettings` to `TestConfigureDescriptionForMcpSettings` **Code quality fixes** - Fixed build errors by removing nullable reference type annotations (project has nullable disabled) - Fixed IDE0090 code style error by using target-typed new expression - Fixed whitespace formatting error by removing trailing whitespace Example configuration: ```json { "runtime": { "mcp": { "enabled": true, "description": "This MCP provides access to the Products database..." } } } ``` ## How was this tested? - [x] Unit Tests - 9 serialization/deserialization tests for MCP description field - 2 CLI configuration tests for description option (consolidated from 3) - All existing ConfigureOptionsTests pass (58 tests) - [x] Build verification - all projects build successfully with no errors or warnings ## Sample Request(s) CLI usage: ```bash dab configure --runtime.mcp.description "This MCP provides access to the Products database and should be used to answer product-related or inventory-related questions from the user." ``` MCP initialize response (when description configured - Stdio Server): ```json { "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2024-11-05", "capabilities": { ... }, "serverInfo": { "name": "SQL MCP Server", "version": "1.0.0" }, "instructions": "This MCP provides access to the Products database..." } } ``` **Note**: The HTTP server currently only updates the server name to "SQL MCP Server". Instructions support will be added when the ModelContextProtocol.AspNetCore library adds support for this field in future versions. <!-- START COPILOT CODING AGENT TIPS --> --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey). --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: JerryNixon <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Aniruddh25 <[email protected]> Co-authored-by: anushakolan <[email protected]> Co-authored-by: Anusha Kolan <[email protected]> Co-authored-by: Aniruddh Munde <[email protected]>
1 parent 6e8ac9f commit f1a0170

7 files changed

Lines changed: 321 additions & 5 deletions

File tree

src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
using System.Security.Claims;
44
using System.Text;
55
using System.Text.Json;
6+
using Azure.DataApiBuilder.Config.ObjectModel;
67
using Azure.DataApiBuilder.Core.AuthenticationHelpers.AuthenticationSimulator;
8+
using Azure.DataApiBuilder.Core.Configurations;
79
using Azure.DataApiBuilder.Mcp.Model;
810
using Microsoft.AspNetCore.Http;
911
using Microsoft.Extensions.Configuration;
@@ -158,7 +160,26 @@ public async Task RunAsync(CancellationToken cancellationToken)
158160
/// </remarks>
159161
private void HandleInitialize(JsonElement? id)
160162
{
161-
var result = new
163+
// Get the description from runtime config if available
164+
string? instructions = null;
165+
RuntimeConfigProvider? runtimeConfigProvider = _serviceProvider.GetService<RuntimeConfigProvider>();
166+
if (runtimeConfigProvider != null)
167+
{
168+
try
169+
{
170+
RuntimeConfig runtimeConfig = runtimeConfigProvider.GetConfig();
171+
instructions = runtimeConfig.Runtime?.Mcp?.Description;
172+
}
173+
catch (Exception ex)
174+
{
175+
// Log to stderr for diagnostics and rethrow to avoid masking configuration errors
176+
Console.Error.WriteLine($"[MCP WARNING] Failed to retrieve MCP description from config: {ex.Message}");
177+
throw;
178+
}
179+
}
180+
181+
// Create the initialize response
182+
object result = new
162183
{
163184
protocolVersion = _protocolVersion,
164185
capabilities = new
@@ -170,7 +191,8 @@ private void HandleInitialize(JsonElement? id)
170191
{
171192
name = McpProtocolDefaults.MCP_SERVER_NAME,
172193
version = McpProtocolDefaults.MCP_SERVER_VERSION
173-
}
194+
},
195+
instructions = !string.IsNullOrWhiteSpace(instructions) ? instructions : null
174196
};
175197

176198
WriteResult(id, result);

src/Cli.Tests/ConfigureOptionsTests.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,34 @@ public void TestFailureWhenAddingSetSessionContextToMySQLDatabase()
926926
Assert.IsFalse(isSuccess);
927927
}
928928

929+
/// <summary>
930+
/// Tests that running "dab configure --runtime.mcp.description {value}" on a config with various values results
931+
/// in runtime config update. Takes in updated value for mcp.description and
932+
/// validates whether the runtime config reflects those updated values
933+
/// </summary>
934+
[DataTestMethod]
935+
[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.")]
936+
[DataRow("Use this server for customer data queries.", DisplayName = "Set MCP description with short text.")]
937+
public void TestConfigureDescriptionForMcpSettings(string descriptionValue)
938+
{
939+
// Arrange -> all the setup which includes creating options.
940+
SetupFileSystemWithInitialConfig(INITIAL_CONFIG);
941+
942+
// Act: Attempts to update mcp.description value
943+
ConfigureOptions options = new(
944+
runtimeMcpDescription: descriptionValue,
945+
config: TEST_RUNTIME_CONFIG_FILE
946+
);
947+
bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!);
948+
949+
// Assert: Validate the Description is updated
950+
Assert.IsTrue(isSuccess);
951+
string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE);
952+
Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(updatedConfig, out RuntimeConfig? runtimeConfig));
953+
Assert.IsNotNull(runtimeConfig.Runtime?.Mcp?.Description);
954+
Assert.AreEqual(descriptionValue, runtimeConfig.Runtime.Mcp.Description);
955+
}
956+
929957
/// <summary>
930958
/// Sets up the mock file system with an initial configuration file.
931959
/// This method adds a config file to the mock file system and verifies its existence.

src/Cli/Commands/ConfigureOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public ConfigureOptions(
3838
bool? runtimeRestRequestBodyStrict = null,
3939
bool? runtimeMcpEnabled = null,
4040
string? runtimeMcpPath = null,
41+
string? runtimeMcpDescription = null,
4142
bool? runtimeMcpDmlToolsEnabled = null,
4243
bool? runtimeMcpDmlToolsDescribeEntitiesEnabled = null,
4344
bool? runtimeMcpDmlToolsCreateRecordEnabled = null,
@@ -93,6 +94,7 @@ public ConfigureOptions(
9394
// Mcp
9495
RuntimeMcpEnabled = runtimeMcpEnabled;
9596
RuntimeMcpPath = runtimeMcpPath;
97+
RuntimeMcpDescription = runtimeMcpDescription;
9698
RuntimeMcpDmlToolsEnabled = runtimeMcpDmlToolsEnabled;
9799
RuntimeMcpDmlToolsDescribeEntitiesEnabled = runtimeMcpDmlToolsDescribeEntitiesEnabled;
98100
RuntimeMcpDmlToolsCreateRecordEnabled = runtimeMcpDmlToolsCreateRecordEnabled;
@@ -180,6 +182,9 @@ public ConfigureOptions(
180182
[Option("runtime.mcp.path", Required = false, HelpText = "Customize DAB's MCP endpoint path. Default: '/mcp' Conditions: Prefix path with '/'.")]
181183
public string? RuntimeMcpPath { get; }
182184

185+
[Option("runtime.mcp.description", Required = false, HelpText = "Set the MCP server description to be exposed in the initialize response.")]
186+
public string? RuntimeMcpDescription { get; }
187+
183188
[Option("runtime.mcp.dml-tools.enabled", Required = false, HelpText = "Enable DAB's MCP DML tools endpoint. Default: true (boolean).")]
184189
public bool? RuntimeMcpDmlToolsEnabled { get; }
185190

src/Cli/ConfigGenerator.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,15 @@ private static bool TryUpdateConfiguredRuntimeOptions(
810810

811811
// MCP: Enabled and Path
812812
if (options.RuntimeMcpEnabled != null ||
813-
options.RuntimeMcpPath != null)
813+
options.RuntimeMcpPath != null ||
814+
options.RuntimeMcpDescription != null ||
815+
options.RuntimeMcpDmlToolsEnabled != null ||
816+
options.RuntimeMcpDmlToolsDescribeEntitiesEnabled != null ||
817+
options.RuntimeMcpDmlToolsCreateRecordEnabled != null ||
818+
options.RuntimeMcpDmlToolsReadRecordsEnabled != null ||
819+
options.RuntimeMcpDmlToolsUpdateRecordEnabled != null ||
820+
options.RuntimeMcpDmlToolsDeleteRecordEnabled != null ||
821+
options.RuntimeMcpDmlToolsExecuteEntityEnabled != null)
814822
{
815823
McpRuntimeOptions updatedMcpOptions = runtimeConfig?.Runtime?.Mcp ?? new();
816824
bool status = TryUpdateConfiguredMcpValues(options, ref updatedMcpOptions);
@@ -1066,6 +1074,14 @@ private static bool TryUpdateConfiguredMcpValues(
10661074
}
10671075
}
10681076

1077+
// Runtime.Mcp.Description
1078+
updatedValue = options?.RuntimeMcpDescription;
1079+
if (updatedValue != null)
1080+
{
1081+
updatedMcpOptions = updatedMcpOptions! with { Description = (string)updatedValue };
1082+
_logger.LogInformation("Updated RuntimeConfig with Runtime.Mcp.Description as '{updatedValue}'", updatedValue);
1083+
}
1084+
10691085
// Handle DML tools configuration
10701086
bool hasToolUpdates = false;
10711087
DmlToolsConfig? currentDmlTools = updatedMcpOptions?.DmlTools;

src/Config/Converters/McpRuntimeOptionsConverterFactory.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,13 @@ internal McpRuntimeOptionsConverter(DeserializationVariableReplacementSettings?
6565
bool enabled = true;
6666
string? path = null;
6767
DmlToolsConfig? dmlTools = null;
68+
string? description = null;
6869

6970
while (reader.Read())
7071
{
7172
if (reader.TokenType == JsonTokenType.EndObject)
7273
{
73-
return new McpRuntimeOptions(enabled, path, dmlTools);
74+
return new McpRuntimeOptions(enabled, path, dmlTools, description);
7475
}
7576

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

102+
case "description":
103+
if (reader.TokenType is not JsonTokenType.Null)
104+
{
105+
description = reader.DeserializeString(_replacementSettings);
106+
}
107+
108+
break;
109+
101110
default:
102111
throw new JsonException($"Unexpected property {propertyName}");
103112
}
@@ -134,6 +143,13 @@ public override void Write(Utf8JsonWriter writer, McpRuntimeOptions value, JsonS
134143
dmlToolsOptionsConverter.Write(writer, value.DmlTools, options);
135144
}
136145

146+
// Write description if it's provided
147+
if (value is not null && !string.IsNullOrWhiteSpace(value.Description))
148+
{
149+
writer.WritePropertyName("description");
150+
JsonSerializer.Serialize(writer, value.Description, options);
151+
}
152+
137153
writer.WriteEndObject();
138154
}
139155
}

src/Config/ObjectModel/McpRuntimeOptions.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,18 @@ public record McpRuntimeOptions
3030
[JsonConverter(typeof(DmlToolsConfigConverter))]
3131
public DmlToolsConfig? DmlTools { get; init; }
3232

33+
/// <summary>
34+
/// Description of the MCP server to be exposed in the initialize response
35+
/// </summary>
36+
[JsonPropertyName("description")]
37+
public string? Description { get; init; }
38+
3339
[JsonConstructor]
3440
public McpRuntimeOptions(
3541
bool? Enabled = null,
3642
string? Path = null,
37-
DmlToolsConfig? DmlTools = null)
43+
DmlToolsConfig? DmlTools = null,
44+
string? Description = null)
3845
{
3946
this.Enabled = Enabled ?? true;
4047

@@ -58,6 +65,8 @@ public McpRuntimeOptions(
5865
{
5966
this.DmlTools = DmlTools;
6067
}
68+
69+
this.Description = Description;
6170
}
6271

6372
/// <summary>

0 commit comments

Comments
 (0)