diff --git a/schemas/dab.draft.schema.json b/schemas/dab.draft.schema.json
index 80cfd953ad..fb759befb7 100644
--- a/schemas/dab.draft.schema.json
+++ b/schemas/dab.draft.schema.json
@@ -693,6 +693,207 @@
}
}
},
+ "autoentities": {
+ "type": "object",
+ "description": "Auto-entity definitions for wildcard pattern matching",
+ "patternProperties": {
+ "^.*$": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "patterns": {
+ "type": "object",
+ "description": "Pattern matching rules for including/excluding database objects",
+ "additionalProperties": false,
+ "properties": {
+ "include": {
+ "type": "array",
+ "description": "T-SQL LIKE pattern to include database objects (e.g., '%.%')",
+ "items": {
+ "type": "string"
+ },
+ "default": null
+ },
+ "exclude": {
+ "type": "array",
+ "description": "T-SQL LIKE pattern to exclude database objects (e.g., 'sales.%')",
+ "items": {
+ "type": "string"
+ },
+ "default": null
+ },
+ "name": {
+ "type": "string",
+ "description": "Interpolation syntax for entity naming, must be unique for every entity inside the pattern",
+ "default": "{schema}{object}"
+ }
+ }
+ },
+ "template": {
+ "type": "object",
+ "description": "Template configuration for generated entities",
+ "additionalProperties": false,
+ "properties": {
+ "mcp": {
+ "type": "object",
+ "description": "MCP endpoint configuration",
+ "additionalProperties": false,
+ "properties": {
+ "dml-tools": {
+ "oneOf": [
+ {
+ "type": "boolean",
+ "description": "Enable/disable all DML tools with default settings."
+ },
+ {
+ "type": "object",
+ "description": "Individual DML tools configuration",
+ "additionalProperties": false,
+ "properties": {
+ "describe-entities": {
+ "type": "boolean",
+ "description": "Enable/disable the describe-entities tool.",
+ "default": false
+ },
+ "create-record": {
+ "type": "boolean",
+ "description": "Enable/disable the create-record tool.",
+ "default": false
+ },
+ "read-records": {
+ "type": "boolean",
+ "description": "Enable/disable the read-records tool.",
+ "default": false
+ },
+ "update-record": {
+ "type": "boolean",
+ "description": "Enable/disable the update-record tool.",
+ "default": false
+ },
+ "delete-record": {
+ "type": "boolean",
+ "description": "Enable/disable the delete-record tool.",
+ "default": false
+ },
+ "execute-entity": {
+ "type": "boolean",
+ "description": "Enable/disable the execute-entity tool.",
+ "default": false
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "rest": {
+ "type": "object",
+ "description": "REST endpoint configuration",
+ "additionalProperties": false,
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "description": "Enable/disable REST endpoint",
+ "default": true
+ }
+ }
+ },
+ "graphql": {
+ "type": "object",
+ "description": "GraphQL endpoint configuration",
+ "additionalProperties": false,
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "description": "Enable/disable GraphQL endpoint",
+ "default": true
+ }
+ }
+ },
+ "health": {
+ "type": "object",
+ "description": "Health check configuration",
+ "additionalProperties": false,
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "description": "Enable/disable health check endpoint",
+ "default": true
+ }
+ }
+ },
+ "cache": {
+ "type": "object",
+ "description": "Cache configuration",
+ "additionalProperties": false,
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "description": "Enable/disable caching",
+ "default": false
+ },
+ "ttl-seconds": {
+ "type": [ "integer", "null" ],
+ "description": "Time-to-live for cached responses in seconds",
+ "default": null,
+ "minimum": 1
+ },
+ "level": {
+ "type": "string",
+ "description": "Cache level (L1 or L1L2)",
+ "enum": [ "L1", "L1L2", null ],
+ "default": null
+ }
+ }
+ }
+ }
+ },
+ "permissions": {
+ "type": "array",
+ "description": "Permissions assigned to this object",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "role": {
+ "type": "string"
+ },
+ "actions": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "[*]"
+ },
+ {
+ "type": "array",
+ "items": {
+ "oneOf": [
+ {
+ "$ref": "#/$defs/action"
+ },
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "action": {
+ "$ref": "#/$defs/action"
+ }
+ }
+ }
+ ]
+ },
+ "uniqueItems": true
+ }
+ ]
+ }
+ }
+ },
+ "required": [ "role", "actions" ]
+ }
+ }
+ }
+ }
+ },
"entities": {
"type": "object",
"description": "Entities that will be exposed via REST and/or GraphQL",
diff --git a/src/Config/Converters/RuntimeAutoEntitiesConverter.cs b/src/Config/Converters/RuntimeAutoEntitiesConverter.cs
new file mode 100644
index 0000000000..bc0abf8ff4
--- /dev/null
+++ b/src/Config/Converters/RuntimeAutoEntitiesConverter.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Azure.DataApiBuilder.Config.ObjectModel;
+
+namespace Azure.DataApiBuilder.Config.Converters;
+
+///
+/// Custom JSON converter for RuntimeAutoEntities.
+///
+class RuntimeAutoEntitiesConverter : JsonConverter
+{
+ ///
+ public override RuntimeAutoEntities? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ Dictionary? autoEntities =
+ JsonSerializer.Deserialize>(ref reader, options);
+
+ return new RuntimeAutoEntities(autoEntities);
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, RuntimeAutoEntities value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ foreach ((string key, AutoEntity autoEntity) in value)
+ {
+ writer.WritePropertyName(key);
+ JsonSerializer.Serialize(writer, autoEntity, options);
+ }
+
+ writer.WriteEndObject();
+ }
+}
diff --git a/src/Config/ObjectModel/AutoEntity.cs b/src/Config/ObjectModel/AutoEntity.cs
new file mode 100644
index 0000000000..b580a8f5c5
--- /dev/null
+++ b/src/Config/ObjectModel/AutoEntity.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+
+namespace Azure.DataApiBuilder.Config.ObjectModel;
+
+///
+/// Defines an individual auto-entity definition with patterns, template, and permissions.
+///
+/// Pattern matching rules for including/excluding database objects
+/// Template configuration for generated entities
+/// Permissions configuration for generated entities (at least one required)
+public record AutoEntity(
+ [property: JsonPropertyName("patterns")] AutoEntityPatterns Patterns,
+ [property: JsonPropertyName("template")] AutoEntityTemplate Template,
+ [property: JsonPropertyName("permissions")] EntityPermission[] Permissions
+);
diff --git a/src/Config/ObjectModel/AutoEntityPatterns.cs b/src/Config/ObjectModel/AutoEntityPatterns.cs
new file mode 100644
index 0000000000..037202a48e
--- /dev/null
+++ b/src/Config/ObjectModel/AutoEntityPatterns.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+
+namespace Azure.DataApiBuilder.Config.ObjectModel;
+
+///
+/// Defines the pattern matching rules for auto-entities.
+///
+/// T-SQL LIKE pattern to include database objects (default: null)
+/// T-SQL LIKE pattern to exclude database objects (default: null)
+/// Interpolation syntax for entity naming (must be unique, default: null)
+public record AutoEntityPatterns(
+ [property: JsonPropertyName("include")] string? Include = null,
+ [property: JsonPropertyName("exclude")] string? Exclude = null,
+ [property: JsonPropertyName("name")] string? Name = null
+);
diff --git a/src/Config/ObjectModel/AutoEntityTemplate.cs b/src/Config/ObjectModel/AutoEntityTemplate.cs
new file mode 100644
index 0000000000..2baefcf52a
--- /dev/null
+++ b/src/Config/ObjectModel/AutoEntityTemplate.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+
+namespace Azure.DataApiBuilder.Config.ObjectModel;
+
+///
+/// Defines the template configuration for auto-entities.
+///
+/// MCP endpoint configuration
+/// REST endpoint configuration
+/// GraphQL endpoint configuration
+/// Health check configuration
+/// Cache configuration
+public record AutoEntityTemplate(
+ [property: JsonPropertyName("mcp")] AutoEntityMcpTemplate? Mcp = null,
+ [property: JsonPropertyName("rest")] AutoEntityRestTemplate? Rest = null,
+ [property: JsonPropertyName("graphql")] AutoEntityGraphQLTemplate? GraphQL = null,
+ [property: JsonPropertyName("health")] AutoEntityHealthTemplate? Health = null,
+ [property: JsonPropertyName("cache")] EntityCacheOptions? Cache = null
+);
+
+///
+/// MCP template configuration for auto-entities.
+///
+/// Enable/disable DML tool (default: true)
+public record AutoEntityMcpTemplate(
+ [property: JsonPropertyName("dml-tool")] bool DmlTool = true
+);
+
+///
+/// REST template configuration for auto-entities.
+///
+/// Enable/disable REST endpoint (default: true)
+public record AutoEntityRestTemplate(
+ [property: JsonPropertyName("enabled")] bool Enabled = true
+);
+
+///
+/// GraphQL template configuration for auto-entities.
+///
+/// Enable/disable GraphQL endpoint (default: true)
+public record AutoEntityGraphQLTemplate(
+ [property: JsonPropertyName("enabled")] bool Enabled = true
+);
+
+///
+/// Health check template configuration for auto-entities.
+///
+/// Enable/disable health check endpoint (default: true)
+public record AutoEntityHealthTemplate(
+ [property: JsonPropertyName("enabled")] bool Enabled = true
+);
diff --git a/src/Config/ObjectModel/RuntimeAutoEntities.cs b/src/Config/ObjectModel/RuntimeAutoEntities.cs
new file mode 100644
index 0000000000..590984c0a9
--- /dev/null
+++ b/src/Config/ObjectModel/RuntimeAutoEntities.cs
@@ -0,0 +1,81 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections;
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json.Serialization;
+using Azure.DataApiBuilder.Config.Converters;
+
+namespace Azure.DataApiBuilder.Config.ObjectModel;
+
+///
+/// Represents a collection of auto-entity definitions.
+/// Each definition is keyed by a unique definition name.
+///
+[JsonConverter(typeof(RuntimeAutoEntitiesConverter))]
+public class RuntimeAutoEntities : IEnumerable>
+{
+ private readonly Dictionary _autoEntities;
+
+ ///
+ /// Creates a new RuntimeAutoEntities collection.
+ ///
+ /// Dictionary of auto-entity definitions keyed by definition name.
+ public RuntimeAutoEntities(Dictionary? autoEntities = null)
+ {
+ _autoEntities = autoEntities ?? new Dictionary();
+ }
+
+ ///
+ /// Gets an auto-entity definition by its definition name.
+ ///
+ /// The name of the auto-entity definition.
+ /// The auto-entity definition.
+ public AutoEntity this[string definitionName] => _autoEntities[definitionName];
+
+ ///
+ /// Tries to get an auto-entity definition by its definition name.
+ ///
+ /// The name of the auto-entity definition.
+ /// The auto-entity definition if found.
+ /// True if the auto-entity definition was found, false otherwise.
+ public bool TryGetValue(string definitionName, [NotNullWhen(true)] out AutoEntity? autoEntity)
+ {
+ return _autoEntities.TryGetValue(definitionName, out autoEntity);
+ }
+
+ ///
+ /// Determines whether an auto-entity definition with the specified name exists.
+ ///
+ /// The name of the auto-entity definition.
+ /// True if the auto-entity definition exists, false otherwise.
+ public bool ContainsKey(string definitionName)
+ {
+ return _autoEntities.ContainsKey(definitionName);
+ }
+
+ ///
+ /// Gets the number of auto-entity definitions in the collection.
+ ///
+ public int Count => _autoEntities.Count;
+
+ ///
+ /// Gets all the auto-entity definition names.
+ ///
+ public IEnumerable Keys => _autoEntities.Keys;
+
+ ///
+ /// Gets all the auto-entity definitions.
+ ///
+ public IEnumerable Values => _autoEntities.Values;
+
+ public IEnumerator> GetEnumerator()
+ {
+ return _autoEntities.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+}
diff --git a/src/Config/ObjectModel/RuntimeConfig.cs b/src/Config/ObjectModel/RuntimeConfig.cs
index a450e1265c..e2905680ae 100644
--- a/src/Config/ObjectModel/RuntimeConfig.cs
+++ b/src/Config/ObjectModel/RuntimeConfig.cs
@@ -25,6 +25,9 @@ public record RuntimeConfig
[JsonPropertyName("azure-key-vault")]
public AzureKeyVaultOptions? AzureKeyVault { get; init; }
+ [JsonPropertyName("autoentities")]
+ public RuntimeAutoEntities? AutoEntities { get; init; }
+
public virtual RuntimeEntities Entities { get; init; }
public DataSourceFiles? DataSourceFiles { get; init; }
diff --git a/src/Service.Tests/Configuration/RuntimeConfigLoaderTests.cs b/src/Service.Tests/Configuration/RuntimeConfigLoaderTests.cs
index 7dcf837d08..9997eda401 100644
--- a/src/Service.Tests/Configuration/RuntimeConfigLoaderTests.cs
+++ b/src/Service.Tests/Configuration/RuntimeConfigLoaderTests.cs
@@ -101,4 +101,37 @@ public async Task FailLoadMultiDataSourceConfigDuplicateEntities(string configPa
Assert.IsTrue(error.StartsWith("Deserialization of the configuration file failed during a post-processing step."));
Assert.IsTrue(error.Contains("An item with the same key has already been added."));
}
+
+ ///
+ /// Test validates that a config file with autoentities section can be loaded successfully.
+ ///
+ [TestMethod]
+ public async Task CanLoadConfigWithAutoEntities()
+ {
+ string configPath = "dab-config.AutoEntities.json";
+ string fileContents = await File.ReadAllTextAsync(configPath);
+
+ IFileSystem fs = new MockFileSystem(new Dictionary() { { configPath, new MockFileData(fileContents) } });
+
+ FileSystemRuntimeConfigLoader loader = new(fs);
+
+ Assert.IsTrue(loader.TryLoadConfig(configPath, out RuntimeConfig runtimeConfig), "Failed to load config with autoentities");
+ Assert.IsNotNull(runtimeConfig.AutoEntities, "AutoEntities should not be null");
+ Assert.AreEqual(2, runtimeConfig.AutoEntities.Count, "Should have 2 auto-entity definitions");
+
+ // Verify first auto-entity definition
+ Assert.IsTrue(runtimeConfig.AutoEntities.ContainsKey("all-tables"), "Should contain 'all-tables' definition");
+ AutoEntity allTables = runtimeConfig.AutoEntities["all-tables"];
+ Assert.AreEqual("%.%", allTables.Patterns.Include, "Include pattern should match");
+ Assert.AreEqual("sys.%", allTables.Patterns.Exclude, "Exclude pattern should match");
+ Assert.AreEqual("{schema}_{object}", allTables.Patterns.Name, "Name pattern should match");
+ Assert.AreEqual(1, allTables.Permissions.Length, "Should have 1 permission");
+
+ // Verify second auto-entity definition
+ Assert.IsTrue(runtimeConfig.AutoEntities.ContainsKey("admin-tables"), "Should contain 'admin-tables' definition");
+ AutoEntity adminTables = runtimeConfig.AutoEntities["admin-tables"];
+ Assert.AreEqual("admin.%", adminTables.Patterns.Include, "Include pattern should match");
+ Assert.IsNull(adminTables.Patterns.Exclude, "Exclude pattern should be null");
+ Assert.AreEqual(1, adminTables.Permissions.Length, "Should have 1 permission");
+ }
}
diff --git a/src/Service.Tests/dab-config.AutoEntities.json b/src/Service.Tests/dab-config.AutoEntities.json
new file mode 100644
index 0000000000..af9820a7be
--- /dev/null
+++ b/src/Service.Tests/dab-config.AutoEntities.json
@@ -0,0 +1,79 @@
+{
+ "$schema": "dab.draft.schema.json",
+ "data-source": {
+ "database-type": "mssql",
+ "connection-string": "Server=localhost;Database=TestDB;User ID=sa;Password=PLACEHOLDER;TrustServerCertificate=true"
+ },
+ "runtime": {
+ "rest": {
+ "path": "/api",
+ "enabled": true
+ },
+ "graphql": {
+ "path": "/graphql",
+ "enabled": true
+ }
+ },
+ "autoentities": {
+ "all-tables": {
+ "patterns": {
+ "include": "%.%",
+ "exclude": "sys.%",
+ "name": "{schema}_{object}"
+ },
+ "template": {
+ "mcp": {
+ "dml-tool": true
+ },
+ "rest": {
+ "enabled": true
+ },
+ "graphql": {
+ "enabled": true
+ },
+ "health": {
+ "enabled": true
+ },
+ "cache": {
+ "enabled": false
+ }
+ },
+ "permissions": [
+ {
+ "role": "anonymous",
+ "actions": ["read"]
+ }
+ ]
+ },
+ "admin-tables": {
+ "patterns": {
+ "include": "admin.%"
+ },
+ "template": {
+ "rest": {
+ "enabled": true
+ },
+ "graphql": {
+ "enabled": false
+ }
+ },
+ "permissions": [
+ {
+ "role": "admin",
+ "actions": ["*"]
+ }
+ ]
+ }
+ },
+ "entities": {
+ "Book": {
+ "source": "books",
+ "permissions": [
+ {
+ "role": "anonymous",
+ "actions": ["read"]
+ }
+ ]
+ }
+ }
+}