Skip to content

Commit 60e33c7

Browse files
CopilotAniruddh25
andcommitted
Add EntityMcpOptions support and fix schema structure
Co-authored-by: Aniruddh25 <[email protected]>
1 parent 8fc87af commit 60e33c7

File tree

7 files changed

+239
-21
lines changed

7 files changed

+239
-21
lines changed

schemas/dab.draft.schema.json

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -695,12 +695,10 @@
695695
},
696696
"autoentities": {
697697
"type": "object",
698-
"description": "Auto-entity definitions for pattern matching",
699-
"patternProperties": {
700-
"^.*$": {
701-
"type": "object",
702-
"additionalProperties": false,
703-
"properties": {
698+
"description": "Defines automatic entity generation rules for MSSQL tables based on include/exclude patterns and defaults.",
699+
"additionalProperties": {
700+
"type": "object",
701+
"properties": {
704702
"patterns": {
705703
"type": "object",
706704
"description": "Pattern matching rules for including/excluding database objects",
@@ -850,7 +848,8 @@
850848
"required": [ "role", "actions" ]
851849
}
852850
}
853-
}
851+
},
852+
"required": [ "permissions" ]
854853
}
855854
},
856855
"entities": {

src/Config/Converters/AutoentityTemplateConverter.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ public AutoentityTemplateConverter(DeserializationVariableReplacementSettings? r
2525
if (reader.TokenType is JsonTokenType.StartObject)
2626
{
2727
// Create converters for each of the sub-properties.
28+
EntityMcpOptionsConverterFactory mcpOptionsConverterFactory = new();
29+
JsonConverter<EntityMcpOptions> mcpOptionsConverter = (JsonConverter<EntityMcpOptions>)(mcpOptionsConverterFactory.CreateConverter(typeof(EntityMcpOptions), options)
30+
?? throw new JsonException("Unable to create converter for EntityMcpOptions"));
31+
2832
EntityRestOptionsConverterFactory restOptionsConverterFactory = new(_replacementSettings);
2933
JsonConverter<EntityRestOptions> restOptionsConverter = (JsonConverter<EntityRestOptions>)(restOptionsConverterFactory.CreateConverter(typeof(EntityRestOptions), options)
3034
?? throw new JsonException("Unable to create converter for EntityRestOptions"));
@@ -42,6 +46,7 @@ public AutoentityTemplateConverter(DeserializationVariableReplacementSettings? r
4246
?? throw new JsonException("Unable to create converter for EntityCacheOptions"));
4347

4448
// Initialize all sub-properties to null.
49+
EntityMcpOptions? mcp = null;
4550
EntityRestOptions? rest = null;
4651
EntityGraphQLOptions? graphQL = null;
4752
EntityHealthCheckConfig? health = null;
@@ -51,14 +56,18 @@ public AutoentityTemplateConverter(DeserializationVariableReplacementSettings? r
5156
{
5257
if (reader.TokenType == JsonTokenType.EndObject)
5358
{
54-
return new AutoentityTemplate(rest, graphQL, health, cache);
59+
return new AutoentityTemplate(mcp, rest, graphQL, health, cache);
5560
}
5661

5762
string? propertyName = reader.GetString();
5863

5964
reader.Read();
6065
switch (propertyName)
6166
{
67+
case "mcp":
68+
mcp = mcpOptionsConverter.Read(ref reader, typeof(EntityMcpOptions), options);
69+
break;
70+
6271
case "rest":
6372
rest = restOptionsConverter.Read(ref reader, typeof(EntityRestOptions), options);
6473
break;
@@ -67,10 +76,6 @@ public AutoentityTemplateConverter(DeserializationVariableReplacementSettings? r
6776
graphQL = graphQLOptionsConverter.Read(ref reader, typeof(EntityGraphQLOptions), options);
6877
break;
6978

70-
case "mcp":
71-
// TODO: Add MCP support for autoentities needed.
72-
break;
73-
7479
case "health":
7580
health = healthOptionsConverter.Read(ref reader, typeof(EntityHealthCheckConfig), options);
7681
break;
@@ -99,6 +104,12 @@ public override void Write(Utf8JsonWriter writer, AutoentityTemplate value, Json
99104
{
100105
writer.WriteStartObject();
101106

107+
if (value?.UserProvidedMcpOptions is true)
108+
{
109+
writer.WritePropertyName("mcp");
110+
JsonSerializer.Serialize(writer, value.Mcp, options);
111+
}
112+
102113
if (value?.UserProvidedRestOptions is true)
103114
{
104115
writer.WritePropertyName("rest");
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Text.Json;
5+
using System.Text.Json.Serialization;
6+
using Azure.DataApiBuilder.Config.ObjectModel;
7+
8+
namespace Azure.DataApiBuilder.Config.Converters;
9+
10+
/// <summary>
11+
/// Factory for creating EntityMcpOptions converters.
12+
/// </summary>
13+
internal class EntityMcpOptionsConverterFactory : JsonConverterFactory
14+
{
15+
public override bool CanConvert(Type typeToConvert)
16+
{
17+
return typeToConvert == typeof(EntityMcpOptions);
18+
}
19+
20+
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
21+
{
22+
return new EntityMcpOptionsConverter();
23+
}
24+
25+
/// <summary>
26+
/// Converter for EntityMcpOptions that handles both boolean and object representations.
27+
/// When boolean: true enables dml-tools and custom-tool remains false (default), false disables dml-tools and custom-tool remains false.
28+
/// When object: can specify individual properties (custom-tool and dml-tools).
29+
/// </summary>
30+
private class EntityMcpOptionsConverter : JsonConverter<EntityMcpOptions>
31+
{
32+
public override EntityMcpOptions? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
33+
{
34+
if (reader.TokenType == JsonTokenType.Null)
35+
{
36+
return null;
37+
}
38+
39+
// Handle boolean shorthand: true/false
40+
if (reader.TokenType == JsonTokenType.True || reader.TokenType == JsonTokenType.False)
41+
{
42+
bool value = reader.GetBoolean();
43+
// Boolean true means: dml-tools=true, custom-tool=false (default)
44+
// Boolean false means: dml-tools=false, custom-tool=false
45+
// Pass null for customToolEnabled to keep it as default (not user-provided)
46+
return new EntityMcpOptions(
47+
customToolEnabled: null,
48+
dmlToolsEnabled: value
49+
);
50+
}
51+
52+
// Handle object representation
53+
if (reader.TokenType == JsonTokenType.StartObject)
54+
{
55+
bool? customToolEnabled = null;
56+
bool? dmlToolsEnabled = null;
57+
58+
while (reader.Read())
59+
{
60+
if (reader.TokenType == JsonTokenType.EndObject)
61+
{
62+
return new EntityMcpOptions(customToolEnabled, dmlToolsEnabled);
63+
}
64+
65+
if (reader.TokenType == JsonTokenType.PropertyName)
66+
{
67+
string? propertyName = reader.GetString();
68+
reader.Read(); // Move to the value
69+
70+
switch (propertyName)
71+
{
72+
case "custom-tool":
73+
customToolEnabled = reader.GetBoolean();
74+
break;
75+
case "dml-tools":
76+
dmlToolsEnabled = reader.GetBoolean();
77+
break;
78+
default:
79+
throw new JsonException($"Unknown property '{propertyName}' in EntityMcpOptions");
80+
}
81+
}
82+
}
83+
}
84+
85+
throw new JsonException($"Unexpected token type {reader.TokenType} for EntityMcpOptions");
86+
}
87+
88+
public override void Write(Utf8JsonWriter writer, EntityMcpOptions value, JsonSerializerOptions options)
89+
{
90+
if (value == null)
91+
{
92+
return;
93+
}
94+
95+
// Check if we should write as boolean shorthand
96+
// Write as boolean if: only dml-tools is set (or custom-tool is default false)
97+
bool writeAsBoolean = !value.UserProvidedCustomToolEnabled && value.UserProvidedDmlToolsEnabled;
98+
99+
if (writeAsBoolean)
100+
{
101+
// Write as boolean shorthand
102+
writer.WriteBooleanValue(value.DmlToolEnabled);
103+
}
104+
else if (value.UserProvidedCustomToolEnabled || value.UserProvidedDmlToolsEnabled)
105+
{
106+
// Write as object
107+
writer.WriteStartObject();
108+
109+
if (value.UserProvidedCustomToolEnabled)
110+
{
111+
writer.WriteBoolean("custom-tool", value.CustomToolEnabled);
112+
}
113+
114+
if (value.UserProvidedDmlToolsEnabled)
115+
{
116+
writer.WriteBoolean("dml-tools", value.DmlToolEnabled);
117+
}
118+
119+
writer.WriteEndObject();
120+
}
121+
}
122+
}
123+
}

src/Config/ObjectModel/AutoentityTemplate.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,30 @@ namespace Azure.DataApiBuilder.Config.ObjectModel;
1616
/// <param name="Cache">Cache configuration</param>
1717
public record AutoentityTemplate
1818
{
19-
// TODO: Will add Mcp variable once MCP is supported at an entity level
20-
// public EntityMcpOptions? Mcp { get; init; }
19+
public EntityMcpOptions? Mcp { get; init; }
2120
public EntityRestOptions Rest { get; init; }
2221
public EntityGraphQLOptions GraphQL { get; init; }
2322
public EntityHealthCheckConfig Health { get; init; }
2423
public EntityCacheOptions Cache { get; init; }
2524

2625
[JsonConstructor]
2726
public AutoentityTemplate(
27+
EntityMcpOptions? Mcp = null,
2828
EntityRestOptions? Rest = null,
2929
EntityGraphQLOptions? GraphQL = null,
3030
EntityHealthCheckConfig? Health = null,
3131
EntityCacheOptions? Cache = null)
3232
{
33+
if (Mcp is not null)
34+
{
35+
this.Mcp = Mcp;
36+
UserProvidedMcpOptions = true;
37+
}
38+
else
39+
{
40+
this.Mcp = null;
41+
}
42+
3343
if (Rest is not null)
3444
{
3545
this.Rest = Rest;
@@ -71,6 +81,20 @@ public AutoentityTemplate(
7181
}
7282
}
7383

84+
/// <summary>
85+
/// Flag which informs CLI and JSON serializer whether to write mcp
86+
/// property and value to the runtime config file.
87+
/// When user doesn't provide the mcp property/value, which signals DAB to use the default,
88+
/// the DAB CLI should not write the default value to a serialized config.
89+
/// This is because the user's intent is to use DAB's default value which could change
90+
/// and DAB CLI writing the property and value would lose the user's intent.
91+
/// This is because if the user were to use the CLI created config, a mcp
92+
/// property/value specified would be interpreted by DAB as "user explicitly set mcp."
93+
/// </summary>
94+
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
95+
[MemberNotNullWhen(true, nameof(Mcp))]
96+
public bool UserProvidedMcpOptions { get; init; } = false;
97+
7498
/// <summary>
7599
/// Flag which informs CLI and JSON serializer whether to write rest
76100
/// property and value to the runtime config file.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Text.Json.Serialization;
5+
6+
namespace Azure.DataApiBuilder.Config.ObjectModel
7+
{
8+
/// <summary>
9+
/// Options for Model Context Protocol (MCP) tools at the entity level.
10+
/// </summary>
11+
public record EntityMcpOptions
12+
{
13+
/// <summary>
14+
/// Indicates whether custom tools are enabled for this entity.
15+
/// Only applicable for stored procedures.
16+
/// </summary>
17+
[JsonPropertyName("custom-tool")]
18+
public bool CustomToolEnabled { get; init; } = false;
19+
20+
/// <summary>
21+
/// Indicates whether DML tools are enabled for this entity.
22+
/// Defaults to true when not explicitly provided.
23+
/// </summary>
24+
[JsonPropertyName("dml-tools")]
25+
public bool DmlToolEnabled { get; init; } = true;
26+
27+
/// <summary>
28+
/// Flag which informs CLI and JSON serializer whether to write the CustomToolEnabled
29+
/// </summary>
30+
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
31+
public bool UserProvidedCustomToolEnabled { get; init; } = false;
32+
33+
/// <summary>
34+
/// Flag which informs CLI and JSON serializer whether to write the DmlToolEnabled
35+
/// </summary>
36+
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
37+
public bool UserProvidedDmlToolsEnabled { get; init; } = false;
38+
39+
/// <summary>
40+
/// Constructor for EntityMcpOptions
41+
/// </summary>
42+
/// <param name="customToolEnabled">The custom tool enabled flag.</param>
43+
/// <param name="dmlToolsEnabled">The DML tools enabled flag.</param>
44+
public EntityMcpOptions(bool? customToolEnabled, bool? dmlToolsEnabled)
45+
{
46+
if (customToolEnabled.HasValue)
47+
{
48+
this.CustomToolEnabled = customToolEnabled.Value;
49+
this.UserProvidedCustomToolEnabled = true;
50+
}
51+
52+
if (dmlToolsEnabled.HasValue)
53+
{
54+
this.DmlToolEnabled = dmlToolsEnabled.Value;
55+
this.UserProvidedDmlToolsEnabled = true;
56+
}
57+
}
58+
}
59+
}

src/Config/RuntimeConfigLoader.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ public static JsonSerializerOptions GetSerializationOptions(
311311
options.Converters.Add(new EntitySourceConverterFactory(replacementSettings));
312312
options.Converters.Add(new EntityGraphQLOptionsConverterFactory(replacementSettings));
313313
options.Converters.Add(new EntityRestOptionsConverterFactory(replacementSettings));
314+
options.Converters.Add(new EntityMcpOptionsConverterFactory());
314315
options.Converters.Add(new EntityActionConverterFactory());
315316
options.Converters.Add(new DataSourceFilesConverter());
316317
options.Converters.Add(new EntityCacheOptionsConverterFactory(replacementSettings));

src/Service.Tests/Caching/DabCacheServiceIntegrationTests.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -759,14 +759,15 @@ private static Mock<RuntimeConfigProvider> CreateMockRuntimeConfigProvider(strin
759759
{ entityName, entity }
760760
});
761761

762+
// Schema, DataSource, Entities, Autoentities, Runtime, DataSourceFiles, AzureKeyVault
762763
Mock<RuntimeConfig> mockRuntimeConfig = new(
763-
string.Empty,
764-
dataSource,
765-
entities,
766-
null,
767-
null,
768-
null,
769-
null
764+
string.Empty, // Schema
765+
dataSource, // DataSource
766+
entities, // Entities
767+
null, // Autoentities
768+
null, // Runtime
769+
null, // DataSourceFiles
770+
null // AzureKeyVault
770771
);
771772
mockRuntimeConfig
772773
.Setup(c => c.GetDataSourceFromDataSourceName(It.IsAny<string>()))

0 commit comments

Comments
 (0)