Skip to content

Commit 8a7adf3

Browse files
Aniruddh25CopilotJerryNixonCopilotanushakolan
authored
Cherry picking server level description to MCP config and fix to allow boolean properties to be set by env vars (#3092)
## Why make this change? - Server level description is an important field in MCP configuration. This should be included in release 1.7. - Fix to allow boolean properties to be set by environment variables is a requested change. ## What is this change? - Cherry picks #3016 and - #3054 ## How was this tested? PR Pipeline validation. --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: JerryNixon <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: anushakolan <[email protected]> Co-authored-by: Anusha Kolan <[email protected]> Co-authored-by: Simon Sabin <[email protected]>
1 parent 346db29 commit 8a7adf3

15 files changed

Lines changed: 646 additions & 23 deletions

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@
1515
*.verified.txt text eol=lf working-tree-encoding=UTF-8
1616
*.verified.xml text eol=lf working-tree-encoding=UTF-8
1717
*.verified.json text eol=lf working-tree-encoding=UTF-8
18+
19+
# This can't be enabled as there are tests that rely on the CRLF endings in files'
20+
#*.cs text eol=lf

CONTRIBUTING.md

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ We use `dotnet format` to enforce code conventions. It is run automatically in C
9595

9696
#### Enforcing code style with git hooks
9797

98-
You can copy paste the following commands to install a git pre-commit hook. This will cause a commit to fail if you forgot to run `dotnet format`. If you have run on save enabled in your editor this is not necessary.
98+
You can copy paste the following commands to install a git pre-commit hook (creates a pre-commit file in your .git folder, which isn't shown in vs code). This will cause a commit to fail if you forgot to run `dotnet format`. If you have run on save enabled in your editor this is not necessary.
9999

100100
```bash
101101
cat > .git/hooks/pre-commit << __EOF__
@@ -112,17 +112,42 @@ if [ "\$(get_files)" = '' ]; then
112112
fi
113113
114114
get_files |
115-
xargs dotnet format src/Azure.DataApiBuilder.Service.sln \\
116-
--check \\
117-
--fix-whitespace --fix-style warn --fix-analyzers warn \\
115+
xargs dotnet format src/Azure.DataApiBuilder.sln \\
116+
--verify-no-changes
118117
--include \\
119118
|| {
120119
get_files |
121-
xargs dotnet format src/Azure.DataApiBuilder.Service.sln \\
122-
--fix-whitespace --fix-style warn --fix-analyzers warn \\
120+
xargs dotnet format src/Azure.DataApiBuilder.sln \\
123121
--include
124122
exit 1
125123
}
126124
__EOF__
127125
chmod +x .git/hooks/pre-commit
128126
```
127+
128+
The file should look like this
129+
130+
``` bash
131+
#!/bin/bash
132+
set -euo pipefail
133+
134+
get_files() {
135+
git diff --cached --name-only --diff-filter=ACMR | \
136+
grep '\.cs$'
137+
}
138+
139+
if [ "$(get_files)" = '' ]; then
140+
exit 0
141+
fi
142+
143+
get_files |
144+
xargs dotnet format src/Azure.DataApiBuilder.sln \
145+
--verify-no-changes \
146+
--include \
147+
|| {
148+
get_files |
149+
xargs dotnet format src/Azure.DataApiBuilder.sln \
150+
--include
151+
exit 1
152+
}
153+
```

schemas/dab.draft.schema.json

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@
4141
"additionalProperties": false,
4242
"properties": {
4343
"enabled": {
44-
"type": "boolean",
45-
"description": "Enable health check endpoint",
44+
"$ref": "#/$defs/boolean-or-string",
45+
"description": "Enable health check endpoint for something",
4646
"default": true,
4747
"additionalProperties": false
4848
},
@@ -186,7 +186,7 @@
186186
"type": "string"
187187
},
188188
"enabled": {
189-
"type": "boolean",
189+
"$ref": "#/$defs/boolean-or-string",
190190
"description": "Allow enabling/disabling REST requests for all entities."
191191
},
192192
"request-body-strict": {
@@ -210,7 +210,7 @@
210210
"type": "string"
211211
},
212212
"enabled": {
213-
"type": "boolean",
213+
"$ref": "#/$defs/boolean-or-string",
214214
"description": "Allow enabling/disabling GraphQL requests for all entities."
215215
},
216216
"depth-limit": {
@@ -438,7 +438,7 @@
438438
"description": "Application Insights connection string"
439439
},
440440
"enabled": {
441-
"type": "boolean",
441+
"$ref": "#/$defs/boolean-or-string",
442442
"description": "Allow enabling/disabling Application Insights telemetry.",
443443
"default": true
444444
}
@@ -481,7 +481,7 @@
481481
"additionalProperties": false,
482482
"properties": {
483483
"enabled": {
484-
"type": "boolean",
484+
"$ref": "#/$defs/boolean-or-string",
485485
"description": "Allow enabling/disabling Azure Log Analytics.",
486486
"default": false
487487
},
@@ -618,7 +618,7 @@
618618
"additionalProperties": false,
619619
"properties": {
620620
"enabled": {
621-
"type": "boolean",
621+
"$ref": "#/$defs/boolean-or-string",
622622
"description": "Enable health check endpoint globally",
623623
"default": true,
624624
"additionalProperties": false
@@ -1179,7 +1179,15 @@
11791179
"type": "string"
11801180
}
11811181
},
1182-
"required": ["singular"]
1182+
"required": [ "singular" ]
1183+
}
1184+
]
1185+
},
1186+
"boolean-or-string": {
1187+
"oneOf":[
1188+
{
1189+
"type": [ "boolean", "string" ],
1190+
"pattern": "^(?:true|false|1|0|@env\\('.*'\\)|@akv\\('.*'\\))$"
11831191
}
11841192
]
11851193
},

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
@@ -797,7 +797,15 @@ private static bool TryUpdateConfiguredRuntimeOptions(
797797

798798
// MCP: Enabled and Path
799799
if (options.RuntimeMcpEnabled != null ||
800-
options.RuntimeMcpPath != null)
800+
options.RuntimeMcpPath != null ||
801+
options.RuntimeMcpDescription != null ||
802+
options.RuntimeMcpDmlToolsEnabled != null ||
803+
options.RuntimeMcpDmlToolsDescribeEntitiesEnabled != null ||
804+
options.RuntimeMcpDmlToolsCreateRecordEnabled != null ||
805+
options.RuntimeMcpDmlToolsReadRecordsEnabled != null ||
806+
options.RuntimeMcpDmlToolsUpdateRecordEnabled != null ||
807+
options.RuntimeMcpDmlToolsDeleteRecordEnabled != null ||
808+
options.RuntimeMcpDmlToolsExecuteEntityEnabled != null)
801809
{
802810
McpRuntimeOptions updatedMcpOptions = runtimeConfig?.Runtime?.Mcp ?? new();
803811
bool status = TryUpdateConfiguredMcpValues(options, ref updatedMcpOptions);
@@ -1053,6 +1061,14 @@ private static bool TryUpdateConfiguredMcpValues(
10531061
}
10541062
}
10551063

1064+
// Runtime.Mcp.Description
1065+
updatedValue = options?.RuntimeMcpDescription;
1066+
if (updatedValue != null)
1067+
{
1068+
updatedMcpOptions = updatedMcpOptions! with { Description = (string)updatedValue };
1069+
_logger.LogInformation("Updated RuntimeConfig with Runtime.Mcp.Description as '{updatedValue}'", updatedValue);
1070+
}
1071+
10561072
// Handle DML tools configuration
10571073
bool hasToolUpdates = false;
10581074
DmlToolsConfig? currentDmlTools = updatedMcpOptions?.DmlTools;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Text.Json;
5+
using System.Text.Json.Serialization;
6+
7+
namespace Azure.DataApiBuilder.Config.Converters;
8+
9+
/// <summary>
10+
/// JSON converter for boolean values that also supports string representations such as
11+
/// "true", "false", "1", and "0". Any environment variable replacement is handled by
12+
/// other converters (for example, the string converter) before the value is parsed here.
13+
/// </summary>
14+
public class BoolJsonConverter : JsonConverter<bool>
15+
{
16+
17+
public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
18+
{
19+
if (reader.TokenType is JsonTokenType.Null)
20+
{
21+
22+
throw new JsonException("Unexpected null JSON token. Expected a boolean literal or a valid @expression.");
23+
}
24+
25+
if (reader.TokenType == JsonTokenType.String)
26+
{
27+
28+
string? tempBoolean = JsonSerializer.Deserialize<string>(ref reader, options);
29+
30+
bool result = tempBoolean?.ToLower() switch
31+
{
32+
//numeric values have to be checked here as they may come from string replacement
33+
"true" or "1" => true,
34+
"false" or "0" => false,
35+
_ => throw new JsonException($"Invalid boolean value: {tempBoolean}. Specify either true or 1 for true, false or 0 for false"),
36+
};
37+
38+
return result;
39+
}
40+
else if (reader.TokenType == JsonTokenType.Number)
41+
{
42+
bool result = reader.GetInt32() switch
43+
{
44+
1 => true,
45+
0 => false,
46+
_ => throw new JsonException($"Invalid value for boolean attribute. Specify either 1 or 0."),
47+
};
48+
return result;
49+
}
50+
else
51+
{
52+
return reader.GetBoolean();
53+
}
54+
}
55+
56+
public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
57+
{
58+
writer.WriteBooleanValue(value);
59+
}
60+
}

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
}

0 commit comments

Comments
 (0)