diff --git a/schemas/dab.draft.schema.json b/schemas/dab.draft.schema.json
index 35f2e08270..b348ac4a4f 100644
--- a/schemas/dab.draft.schema.json
+++ b/schemas/dab.draft.schema.json
@@ -158,13 +158,13 @@
"type": "object",
"properties": {
"max-page-size": {
- "type": ["integer", "null"],
+ "type": [ "integer", "null" ],
"description": "Defines the maximum number of records that can be returned in a single page of results. If set to null, the default value is 100,000.",
"default": 100000,
"minimum": 1
},
"default-page-size": {
- "type": ["integer", "null"],
+ "type": [ "integer", "null" ],
"description": "Sets the default number of records returned in a single response. When this limit is reached, a continuation token is provided to retrieve the next page. If set to null, the default value is 100.",
"default": 100,
"minimum": 1
@@ -214,7 +214,7 @@
"description": "Allow enabling/disabling GraphQL requests for all entities."
},
"depth-limit": {
- "type": ["integer", "null"],
+ "type": [ "integer", "null" ],
"description": "Maximum allowed depth of a GraphQL query.",
"default": null
},
@@ -239,13 +239,74 @@
}
}
},
+ "mcp": {
+ "type": "object",
+ "description": "Global MCP endpoint configuration",
+ "additionalProperties": false,
+ "properties": {
+ "path": {
+ "default": "/mcp",
+ "type": "string"
+ },
+ "enabled": {
+ "type": "boolean",
+ "description": "Allow enabling/disabling MCP requests for all entities.",
+ "default": true
+ },
+ "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
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
"host": {
"type": "object",
"description": "Global hosting configuration",
"additionalProperties": false,
"properties": {
"max-response-size-mb": {
- "type": ["integer", "null"],
+ "type": [ "integer", "null" ],
"description": "Specifies the maximum size, in megabytes, of the database response allowed in a single result. If set to null, the default value is 158 MB.",
"default": 158,
"minimum": 1,
@@ -253,12 +314,12 @@
},
"mode": {
"description": "Set if running in Development or Production mode",
- "type": ["string", "null"],
+ "type": [ "string", "null" ],
"default": "production",
- "enum": ["production", "development"]
+ "enum": [ "production", "development" ]
},
"cors": {
- "type": ["object", "null"],
+ "type": [ "object", "null" ],
"description": "Configure CORS",
"additionalProperties": false,
"properties": {
@@ -278,7 +339,7 @@
}
},
"authentication": {
- "type": ["object", "null"],
+ "type": [ "object", "null" ],
"additionalProperties": false,
"properties": {
"provider": {
@@ -322,7 +383,7 @@
"type": "string"
}
},
- "required": ["audience", "issuer"]
+ "required": [ "audience", "issuer" ]
}
},
"allOf": [
@@ -338,9 +399,9 @@
]
}
},
- "required": ["provider"]
+ "required": [ "provider" ]
},
- "then": { "required": ["jwt"] },
+ "then": { "required": [ "jwt" ] },
"else": { "properties": { "jwt": false } }
}
]
@@ -382,7 +443,7 @@
"default": true
}
},
- "required": ["connection-string"]
+ "required": [ "connection-string" ]
},
"open-telemetry": {
"type": "object",
@@ -405,7 +466,7 @@
"type": "string",
"description": "Open Telemetry protocol",
"default": "grpc",
- "enum": ["grpc", "httpprotobuf"]
+ "enum": [ "grpc", "httpprotobuf" ]
},
"enabled": {
"type": "boolean",
@@ -413,7 +474,7 @@
"default": true
}
},
- "required": ["endpoint"]
+ "required": [ "endpoint" ]
},
"azure-log-analytics": {
"type": "object",
diff --git a/src/Azure.DataApiBuilder.Mcp/Azure.DataApiBuilder.Mcp.csproj b/src/Azure.DataApiBuilder.Mcp/Azure.DataApiBuilder.Mcp.csproj
new file mode 100644
index 0000000000..f675f8d8d1
--- /dev/null
+++ b/src/Azure.DataApiBuilder.Mcp/Azure.DataApiBuilder.Mcp.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Azure.DataApiBuilder.Mcp/BuiltInTools/CreateRecordTool.cs b/src/Azure.DataApiBuilder.Mcp/BuiltInTools/CreateRecordTool.cs
new file mode 100644
index 0000000000..ed5425c515
--- /dev/null
+++ b/src/Azure.DataApiBuilder.Mcp/BuiltInTools/CreateRecordTool.cs
@@ -0,0 +1,87 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json;
+using Azure.DataApiBuilder.Mcp.Model;
+using ModelContextProtocol.Protocol;
+using static Azure.DataApiBuilder.Mcp.Model.McpEnums;
+
+namespace Azure.DataApiBuilder.Mcp.BuiltInTools
+{
+ public class CreateRecordTool : IMcpTool
+ {
+ public ToolType ToolType { get; } = ToolType.BuiltIn;
+
+ public Tool GetToolMetadata()
+ {
+ return new Tool
+ {
+ Name = "create-record",
+ Description = "Creates a new record in the specified entity.",
+ InputSchema = JsonSerializer.Deserialize(
+ @"{
+ ""type"": ""object"",
+ ""properties"": {
+ ""entity"": {
+ ""type"": ""string"",
+ ""description"": ""The name of the entity""
+ },
+ ""data"": {
+ ""type"": ""object"",
+ ""description"": ""The data for the new record""
+ }
+ },
+ ""required"": [""entity"", ""data""]
+ }"
+ )
+ };
+ }
+
+ public Task ExecuteAsync(
+ JsonDocument? arguments,
+ IServiceProvider serviceProvider,
+ CancellationToken cancellationToken = default)
+ {
+ if (arguments == null)
+ {
+ return Task.FromResult(new CallToolResult
+ {
+ Content = [new TextContentBlock { Type = "text", Text = "Error: No arguments provided" }]
+ });
+ }
+
+ try
+ {
+ // Extract arguments
+ JsonElement root = arguments.RootElement;
+
+ if (!root.TryGetProperty("entity", out JsonElement entityElement) ||
+ !root.TryGetProperty("data", out JsonElement dataElement))
+ {
+ return Task.FromResult(new CallToolResult
+ {
+ Content = [new TextContentBlock { Type = "text", Text = "Error: Missing required arguments 'entity' or 'data'" }]
+ });
+ }
+
+ string entityName = entityElement.GetString() ?? string.Empty;
+
+ // TODO: Implement actual create logic using DAB's internal services
+ // For now, return a placeholder response
+ string result = $"Would create record in entity '{entityName}' with data: {dataElement.GetRawText()}";
+
+ return Task.FromResult(new CallToolResult
+ {
+ Content = [new TextContentBlock { Type = "text", Text = result }]
+ });
+ }
+ catch (Exception ex)
+ {
+ return Task.FromResult(new CallToolResult
+ {
+ Content = [new TextContentBlock { Type = "text", Text = $"Error: {ex.Message}" }]
+ });
+ }
+ }
+ }
+}
diff --git a/src/Azure.DataApiBuilder.Mcp/BuiltInTools/DescribeEntitiesTool.cs b/src/Azure.DataApiBuilder.Mcp/BuiltInTools/DescribeEntitiesTool.cs
new file mode 100644
index 0000000000..3e7ade6075
--- /dev/null
+++ b/src/Azure.DataApiBuilder.Mcp/BuiltInTools/DescribeEntitiesTool.cs
@@ -0,0 +1,83 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json;
+using Azure.DataApiBuilder.Config.ObjectModel;
+using Azure.DataApiBuilder.Core.Configurations;
+using Azure.DataApiBuilder.Mcp.Model;
+using Microsoft.Extensions.DependencyInjection;
+using ModelContextProtocol.Protocol;
+using static Azure.DataApiBuilder.Mcp.Model.McpEnums;
+
+namespace Azure.DataApiBuilder.Mcp.BuiltInTools
+{
+ public class DescribeEntitiesTool : IMcpTool
+ {
+ public ToolType ToolType { get; } = ToolType.BuiltIn;
+
+ public Tool GetToolMetadata()
+ {
+ return new Tool
+ {
+ Name = "describe-entities",
+ Description = "Lists and describes all entities in the database."
+ };
+ }
+
+ public Task ExecuteAsync(
+ JsonDocument? arguments,
+ IServiceProvider serviceProvider,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ // Get the runtime config provider
+ RuntimeConfigProvider? runtimeConfigProvider = serviceProvider.GetService();
+ if (runtimeConfigProvider == null || !runtimeConfigProvider.TryGetConfig(out RuntimeConfig? runtimeConfig))
+ {
+ return Task.FromResult(new CallToolResult
+ {
+ Content = [new TextContentBlock { Type = "text", Text = "Error: Runtime configuration not available." }]
+ });
+ }
+
+ // Extract entity information from the runtime config
+ Dictionary entities = new();
+
+ if (runtimeConfig.Entities != null)
+ {
+ foreach (KeyValuePair entity in runtimeConfig.Entities)
+ {
+ entities[entity.Key] = new
+ {
+ source = entity.Value.Source,
+ permissions = entity.Value.Permissions?.Select(p => new
+ {
+ role = p.Role,
+ actions = p.Actions
+ })
+ };
+ }
+ }
+
+ string entitiesJson = JsonSerializer.Serialize(entities, new JsonSerializerOptions
+ {
+ WriteIndented = true,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ });
+
+ return Task.FromResult(new CallToolResult
+ {
+ Content = [new TextContentBlock { Type = "application/json", Text = entitiesJson }]
+ });
+ }
+ catch (Exception ex)
+ {
+ return Task.FromResult(new CallToolResult
+ {
+ Content = [new TextContentBlock { Type = "text", Text = $"Error: {ex.Message}" }]
+ });
+ }
+ }
+ }
+}
diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpEndpointRouteBuilderExtensions.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpEndpointRouteBuilderExtensions.cs
new file mode 100644
index 0000000000..6401e17e22
--- /dev/null
+++ b/src/Azure.DataApiBuilder.Mcp/Core/McpEndpointRouteBuilderExtensions.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Diagnostics.CodeAnalysis;
+using Azure.DataApiBuilder.Config.ObjectModel;
+using Azure.DataApiBuilder.Core.Configurations;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Routing;
+
+namespace Azure.DataApiBuilder.Mcp.Core
+{
+ ///
+ /// Extension methods for mapping MCP endpoints to an .
+ ///
+ public static class McpEndpointRouteBuilderExtensions
+ {
+ ///
+ /// Maps the MCP endpoint to the specified if MCP is enabled in the runtime configuration.
+ ///
+ public static IEndpointRouteBuilder MapDabMcp(
+ this IEndpointRouteBuilder endpoints,
+ RuntimeConfigProvider runtimeConfigProvider,
+ [StringSyntax("Route")] string pattern = "")
+ {
+ if (!TryGetMcpOptions(runtimeConfigProvider, out McpRuntimeOptions? mcpOptions) || mcpOptions == null || !mcpOptions.Enabled)
+ {
+ return endpoints;
+ }
+
+ string mcpPath = mcpOptions.Path ?? McpRuntimeOptions.DEFAULT_PATH;
+
+ // Map the MCP endpoint
+ endpoints.MapMcp(mcpPath);
+
+ return endpoints;
+ }
+
+ ///
+ /// Gets MCP options from the runtime configuration
+ ///
+ /// Runtime config provider
+ /// MCP options
+ /// True if MCP options were found, false otherwise
+ private static bool TryGetMcpOptions(RuntimeConfigProvider runtimeConfigProvider, out McpRuntimeOptions? mcpOptions)
+ {
+ mcpOptions = null;
+
+ if (!runtimeConfigProvider.TryGetConfig(out RuntimeConfig? runtimeConfig))
+ {
+ return false;
+ }
+
+ mcpOptions = runtimeConfig?.Runtime?.Mcp;
+ return mcpOptions != null;
+ }
+ }
+}
diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpServerConfiguration.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpServerConfiguration.cs
new file mode 100644
index 0000000000..86cccd2aaf
--- /dev/null
+++ b/src/Azure.DataApiBuilder.Mcp/Core/McpServerConfiguration.cs
@@ -0,0 +1,94 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json;
+using Azure.DataApiBuilder.Mcp.Model;
+using Microsoft.Extensions.DependencyInjection;
+using ModelContextProtocol;
+using ModelContextProtocol.Protocol;
+
+namespace Azure.DataApiBuilder.Mcp.Core
+{
+ ///
+ /// Configuration for MCP server capabilities and handlers
+ ///
+ internal static class McpServerConfiguration
+ {
+ ///
+ /// Configures the MCP server with tool capabilities
+ ///
+ internal static IServiceCollection ConfigureMcpServer(this IServiceCollection services)
+ {
+ services.AddMcpServer(options =>
+ {
+ options.ServerInfo = new() { Name = "Data API builder MCP Server", Version = "1.0.0" };
+ options.Capabilities = new()
+ {
+ Tools = new()
+ {
+ ListToolsHandler = (request, ct) =>
+ {
+ McpToolRegistry? toolRegistry = request.Services?.GetRequiredService();
+ if (toolRegistry == null)
+ {
+ throw new InvalidOperationException("Tool registry is not available.");
+ }
+
+ List tools = toolRegistry.GetAllTools().ToList();
+
+ return ValueTask.FromResult(new ListToolsResult
+ {
+ Tools = tools
+ });
+ },
+ CallToolHandler = async (request, ct) =>
+ {
+ McpToolRegistry? toolRegistry = request.Services?.GetRequiredService();
+ if (toolRegistry == null)
+ {
+ throw new InvalidOperationException("Tool registry is not available.");
+ }
+
+ string? toolName = request.Params?.Name;
+ if (string.IsNullOrEmpty(toolName))
+ {
+ throw new McpException("Tool name is required.");
+ }
+
+ if (!toolRegistry.TryGetTool(toolName, out IMcpTool? tool))
+ {
+ throw new McpException($"Unknown tool: '{toolName}'");
+ }
+
+ JsonDocument? arguments = null;
+ if (request.Params?.Arguments != null)
+ {
+ // Convert IReadOnlyDictionary to JsonDocument
+ Dictionary jsonObject = new();
+ foreach (KeyValuePair kvp in request.Params.Arguments)
+ {
+ jsonObject[kvp.Key] = kvp.Value;
+ }
+
+ string json = JsonSerializer.Serialize(jsonObject);
+ arguments = JsonDocument.Parse(json);
+ }
+
+ try
+ {
+ return await tool!.ExecuteAsync(arguments, request.Services!, ct);
+ }
+ finally
+ {
+ arguments?.Dispose();
+ }
+ }
+ }
+ };
+ })
+ .WithHttpTransport();
+
+ return services;
+ }
+ }
+}
diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpServiceCollectionExtensions.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpServiceCollectionExtensions.cs
new file mode 100644
index 0000000000..01f6015786
--- /dev/null
+++ b/src/Azure.DataApiBuilder.Mcp/Core/McpServiceCollectionExtensions.cs
@@ -0,0 +1,65 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Reflection;
+using Azure.DataApiBuilder.Config.ObjectModel;
+using Azure.DataApiBuilder.Core.Configurations;
+using Azure.DataApiBuilder.Mcp.Model;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Azure.DataApiBuilder.Mcp.Core
+{
+ ///
+ /// Extension methods for configuring MCP services in the DI container
+ ///
+ public static class McpServiceCollectionExtensions
+ {
+ ///
+ /// Adds MCP server and related services to the service collection
+ ///
+ public static IServiceCollection AddDabMcpServer(this IServiceCollection services, RuntimeConfigProvider runtimeConfigProvider)
+ {
+ if (!runtimeConfigProvider.TryGetConfig(out RuntimeConfig? runtimeConfig))
+ {
+ // If config is not available, skip MCP setup
+ return services;
+ }
+
+ // Only add MCP server if it's enabled in the configuration
+ if (!runtimeConfig.IsMcpEnabled)
+ {
+ return services;
+ }
+
+ // Register core MCP services
+ services.AddSingleton();
+ services.AddHostedService();
+
+ // Auto-discover and register all MCP tools
+ RegisterAllMcpTools(services);
+
+ // Configure MCP server
+ services.ConfigureMcpServer();
+
+ return services;
+ }
+
+ ///
+ /// Automatically discovers and registers all classes implementing IMcpTool
+ ///
+ private static void RegisterAllMcpTools(IServiceCollection services)
+ {
+ Assembly mcpAssembly = typeof(IMcpTool).Assembly;
+
+ IEnumerable toolTypes = mcpAssembly.GetTypes()
+ .Where(t => t.IsClass &&
+ !t.IsAbstract &&
+ typeof(IMcpTool).IsAssignableFrom(t));
+
+ foreach (Type toolType in toolTypes)
+ {
+ services.AddSingleton(typeof(IMcpTool), toolType);
+ }
+ }
+ }
+}
diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpToolRegistry.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpToolRegistry.cs
new file mode 100644
index 0000000000..9c9b96d72b
--- /dev/null
+++ b/src/Azure.DataApiBuilder.Mcp/Core/McpToolRegistry.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Azure.DataApiBuilder.Mcp.Model;
+using ModelContextProtocol.Protocol;
+
+namespace Azure.DataApiBuilder.Mcp.Core
+{
+ ///
+ /// Registry for managing MCP tools
+ ///
+ public class McpToolRegistry
+ {
+ private readonly Dictionary _tools = new();
+
+ ///
+ /// Registers a tool in the registry
+ ///
+ public void RegisterTool(IMcpTool tool)
+ {
+ Tool metadata = tool.GetToolMetadata();
+ _tools[metadata.Name] = tool;
+ }
+
+ ///
+ /// Gets all registered tools
+ ///
+ public IEnumerable GetAllTools()
+ {
+ return _tools.Values.Select(t => t.GetToolMetadata());
+ }
+
+ ///
+ /// Tries to get a tool by name
+ ///
+ public bool TryGetTool(string toolName, out IMcpTool? tool)
+ {
+ return _tools.TryGetValue(toolName, out tool);
+ }
+ }
+}
diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpToolRegistryInitializer.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpToolRegistryInitializer.cs
new file mode 100644
index 0000000000..97d0dac7f3
--- /dev/null
+++ b/src/Azure.DataApiBuilder.Mcp/Core/McpToolRegistryInitializer.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Azure.DataApiBuilder.Mcp.Model;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Azure.DataApiBuilder.Mcp.Core
+{
+ ///
+ /// Hosted service to initialize the MCP tool registry
+ ///
+ public class McpToolRegistryInitializer : IHostedService
+ {
+ private readonly IServiceProvider _serviceProvider;
+ private readonly McpToolRegistry _toolRegistry;
+
+ public McpToolRegistryInitializer(IServiceProvider serviceProvider, McpToolRegistry toolRegistry)
+ {
+ _serviceProvider = serviceProvider;
+ _toolRegistry = toolRegistry;
+ }
+
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ // Register all IMcpTool implementations
+ IEnumerable tools = _serviceProvider.GetServices();
+ foreach (IMcpTool tool in tools)
+ {
+ _toolRegistry.RegisterTool(tool);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/Azure.DataApiBuilder.Mcp/Model/Enums.cs b/src/Azure.DataApiBuilder.Mcp/Model/Enums.cs
new file mode 100644
index 0000000000..84ca49e1b0
--- /dev/null
+++ b/src/Azure.DataApiBuilder.Mcp/Model/Enums.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Azure.DataApiBuilder.Mcp.Model
+{
+ public class McpEnums
+ {
+ ///
+ /// Specifies the type of tool.
+ ///
+ /// This enumeration defines whether a tool is a built-in tool provided by the system or
+ /// a custom tool defined by the user.
+ public enum ToolType
+ {
+ BuiltIn,
+ Custom
+ }
+ }
+}
diff --git a/src/Azure.DataApiBuilder.Mcp/Model/IMcpTool.cs b/src/Azure.DataApiBuilder.Mcp/Model/IMcpTool.cs
new file mode 100644
index 0000000000..bbee6a9304
--- /dev/null
+++ b/src/Azure.DataApiBuilder.Mcp/Model/IMcpTool.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json;
+using ModelContextProtocol.Protocol;
+using static Azure.DataApiBuilder.Mcp.Model.McpEnums;
+
+namespace Azure.DataApiBuilder.Mcp.Model
+{
+ ///
+ /// Interface for MCP tool implementations
+ ///
+ public interface IMcpTool
+ {
+ ///
+ /// Gets the type of the tool.
+ ///
+ ToolType ToolType { get; }
+
+ ///
+ /// Gets the tool metadata
+ ///
+ Tool GetToolMetadata();
+
+ ///
+ /// Executes the tool with the provided arguments
+ ///
+ /// The JSON arguments passed to the tool
+ /// The service provider for resolving dependencies
+ /// Cancellation token
+ /// The tool execution result
+ Task ExecuteAsync(
+ JsonDocument? arguments,
+ IServiceProvider serviceProvider,
+ CancellationToken cancellationToken = default);
+ }
+}
diff --git a/src/Azure.DataApiBuilder.sln b/src/Azure.DataApiBuilder.sln
index e7f61fa3ed..aa3c8e2bad 100644
--- a/src/Azure.DataApiBuilder.sln
+++ b/src/Azure.DataApiBuilder.sln
@@ -31,6 +31,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.DataApiBuilder.Core",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.DataApiBuilder.Product", "Product\Azure.DataApiBuilder.Product.csproj", "{E3D2076C-EE49-43A0-8F92-5FC41EC99DA7}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.DataApiBuilder.Mcp", "Azure.DataApiBuilder.Mcp\Azure.DataApiBuilder.Mcp.csproj", "{A287E849-A043-4F37-BC40-A87C4705F583}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -73,6 +75,10 @@ Global
{E3D2076C-EE49-43A0-8F92-5FC41EC99DA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3D2076C-EE49-43A0-8F92-5FC41EC99DA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3D2076C-EE49-43A0-8F92-5FC41EC99DA7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A287E849-A043-4F37-BC40-A87C4705F583}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A287E849-A043-4F37-BC40-A87C4705F583}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A287E849-A043-4F37-BC40-A87C4705F583}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A287E849-A043-4F37-BC40-A87C4705F583}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Cli.Tests/ConfigGeneratorTests.cs b/src/Cli.Tests/ConfigGeneratorTests.cs
index 6094189f93..58e006b75d 100644
--- a/src/Cli.Tests/ConfigGeneratorTests.cs
+++ b/src/Cli.Tests/ConfigGeneratorTests.cs
@@ -163,6 +163,10 @@ public void TestSpecialCharactersInConnectionString()
""path"": ""/An_"",
""allow-introspection"": true
},
+ ""mcp"": {
+ ""enabled"": true,
+ ""path"": ""/mcp""
+ },
""host"": {
""cors"": {
""origins"": [],
diff --git a/src/Cli.Tests/ExporterTests.cs b/src/Cli.Tests/ExporterTests.cs
index aecd6455a3..3735dc43a1 100644
--- a/src/Cli.Tests/ExporterTests.cs
+++ b/src/Cli.Tests/ExporterTests.cs
@@ -21,7 +21,7 @@ public void ExportGraphQLFromDabService_LogsWhenHttpsWorks()
RuntimeConfig runtimeConfig = new(
Schema: "schema",
DataSource: new DataSource(DatabaseType.MSSQL, "", new()),
- Runtime: new(Rest: new(), GraphQL: new(), Host: new(null, null)),
+ Runtime: new(Rest: new(), GraphQL: new(), Mcp: new(), Host: new(null, null)),
Entities: new(new Dictionary())
);
@@ -59,7 +59,7 @@ public void ExportGraphQLFromDabService_LogsFallbackToHttp_WhenHttpsFails()
RuntimeConfig runtimeConfig = new(
Schema: "schema",
DataSource: new DataSource(DatabaseType.MSSQL, "", new()),
- Runtime: new(Rest: new(), GraphQL: new(), Host: new(null, null)),
+ Runtime: new(Rest: new(), GraphQL: new(), Mcp: new(), Host: new(null, null)),
Entities: new(new Dictionary())
);
@@ -105,7 +105,7 @@ public void ExportGraphQLFromDabService_ThrowsException_WhenBothHttpsAndHttpFail
RuntimeConfig runtimeConfig = new(
Schema: "schema",
DataSource: new DataSource(DatabaseType.MSSQL, "", new()),
- Runtime: new(Rest: new(), GraphQL: new(), Host: new(null, null)),
+ Runtime: new(Rest: new(), GraphQL: new(), Mcp: new(), Host: new(null, null)),
Entities: new(new Dictionary())
);
diff --git a/src/Cli.Tests/ModuleInitializer.cs b/src/Cli.Tests/ModuleInitializer.cs
index 2cfba899ea..e00dc00a89 100644
--- a/src/Cli.Tests/ModuleInitializer.cs
+++ b/src/Cli.Tests/ModuleInitializer.cs
@@ -47,6 +47,10 @@ public static void Init()
VerifierSettings.IgnoreMember(options => options.IsGraphQLEnabled);
// Ignore the entity IsGraphQLEnabled as that's unimportant from a test standpoint.
VerifierSettings.IgnoreMember(entity => entity.IsGraphQLEnabled);
+ // Ignore the global IsMcpEnabled as that's unimportant from a test standpoint.
+ VerifierSettings.IgnoreMember(config => config.IsMcpEnabled);
+ // Ignore the global RuntimeOptions.IsMcpEnabled as that's unimportant from a test standpoint.
+ VerifierSettings.IgnoreMember(options => options.IsMcpEnabled);
// Ignore the global IsHealthEnabled as that's unimportant from a test standpoint.
VerifierSettings.IgnoreMember(config => config.IsHealthEnabled);
// Ignore the global RuntimeOptions.IsHealthCheckEnabled as that's unimportant from a test standpoint.
@@ -67,12 +71,18 @@ public static void Init()
VerifierSettings.IgnoreMember(config => config.IsGraphQLEnabled);
// Ignore the IsRestEnabled as that's unimportant from a test standpoint.
VerifierSettings.IgnoreMember(config => config.IsRestEnabled);
+ // Ignore the IsMcpEnabled as that's unimportant from a test standpoint.
+ VerifierSettings.IgnoreMember(config => config.IsMcpEnabled);
+ // Ignore the McpDmlTools as that's unimportant from a test standpoint.
+ VerifierSettings.IgnoreMember(config => config.McpDmlTools);
// Ignore the IsStaticWebAppsIdentityProvider as that's unimportant from a test standpoint.
VerifierSettings.IgnoreMember(config => config.IsStaticWebAppsIdentityProvider);
// Ignore the RestPath as that's unimportant from a test standpoint.
VerifierSettings.IgnoreMember(config => config.RestPath);
// Ignore the GraphQLPath as that's unimportant from a test standpoint.
VerifierSettings.IgnoreMember(config => config.GraphQLPath);
+ // Ignore the McpPath as that's unimportant from a test standpoint.
+ VerifierSettings.IgnoreMember(config => config.McpPath);
// Ignore the AllowIntrospection as that's unimportant from a test standpoint.
VerifierSettings.IgnoreMember(config => config.AllowIntrospection);
// Ignore the EnableAggregation as that's unimportant from a test standpoint.
@@ -101,6 +111,8 @@ public static void Init()
VerifierSettings.IgnoreMember(options => options.UserProvidedDepthLimit);
// Ignore EnableLegacyDateTimeScalar as that's not serialized in our config file.
VerifierSettings.IgnoreMember(options => options.EnableLegacyDateTimeScalar);
+ // Ignore UserProvidedPath as that's not serialized in our config file.
+ VerifierSettings.IgnoreMember(options => options.UserProvidedPath);
// Customise the path where we store snapshots, so they are easier to locate in a PR review.
VerifyBase.DerivePathInfo(
(sourceFile, projectDirectory, type, method) => new(
diff --git a/src/Cli.Tests/Snapshots/EndToEndTests.TestAddingStoredProcedureWithRestMethodsAndGraphQLOperations.verified.txt b/src/Cli.Tests/Snapshots/EndToEndTests.TestAddingStoredProcedureWithRestMethodsAndGraphQLOperations.verified.txt
index a76f72b9a0..226c4e2a20 100644
--- a/src/Cli.Tests/Snapshots/EndToEndTests.TestAddingStoredProcedureWithRestMethodsAndGraphQLOperations.verified.txt
+++ b/src/Cli.Tests/Snapshots/EndToEndTests.TestAddingStoredProcedureWithRestMethodsAndGraphQLOperations.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
AllowCredentials: false
diff --git a/src/Cli.Tests/Snapshots/EndToEndTests.TestConfigGeneratedAfterAddingEntityWithSourceAsStoredProcedure.verified.txt b/src/Cli.Tests/Snapshots/EndToEndTests.TestConfigGeneratedAfterAddingEntityWithSourceAsStoredProcedure.verified.txt
index 95415c1685..c4eb43648c 100644
--- a/src/Cli.Tests/Snapshots/EndToEndTests.TestConfigGeneratedAfterAddingEntityWithSourceAsStoredProcedure.verified.txt
+++ b/src/Cli.Tests/Snapshots/EndToEndTests.TestConfigGeneratedAfterAddingEntityWithSourceAsStoredProcedure.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
AllowCredentials: false
diff --git a/src/Cli.Tests/Snapshots/EndToEndTests.TestConfigGeneratedAfterAddingEntityWithSourceWithDefaultType.verified.txt b/src/Cli.Tests/Snapshots/EndToEndTests.TestConfigGeneratedAfterAddingEntityWithSourceWithDefaultType.verified.txt
index ee8dbf6199..a77ecc134b 100644
--- a/src/Cli.Tests/Snapshots/EndToEndTests.TestConfigGeneratedAfterAddingEntityWithSourceWithDefaultType.verified.txt
+++ b/src/Cli.Tests/Snapshots/EndToEndTests.TestConfigGeneratedAfterAddingEntityWithSourceWithDefaultType.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
AllowCredentials: false
diff --git a/src/Cli.Tests/Snapshots/EndToEndTests.TestConfigGeneratedAfterAddingEntityWithoutIEnumerables.verified.txt b/src/Cli.Tests/Snapshots/EndToEndTests.TestConfigGeneratedAfterAddingEntityWithoutIEnumerables.verified.txt
index 0d0afda2bf..a19694b688 100644
--- a/src/Cli.Tests/Snapshots/EndToEndTests.TestConfigGeneratedAfterAddingEntityWithoutIEnumerables.verified.txt
+++ b/src/Cli.Tests/Snapshots/EndToEndTests.TestConfigGeneratedAfterAddingEntityWithoutIEnumerables.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
AllowCredentials: false
diff --git a/src/Cli.Tests/Snapshots/EndToEndTests.TestInitForCosmosDBNoSql.verified.txt b/src/Cli.Tests/Snapshots/EndToEndTests.TestInitForCosmosDBNoSql.verified.txt
index cbb2df5fb8..081c5f8e55 100644
--- a/src/Cli.Tests/Snapshots/EndToEndTests.TestInitForCosmosDBNoSql.verified.txt
+++ b/src/Cli.Tests/Snapshots/EndToEndTests.TestInitForCosmosDBNoSql.verified.txt
@@ -17,6 +17,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/EndToEndTests.TestUpdatingStoredProcedureWithRestMethods.verified.txt b/src/Cli.Tests/Snapshots/EndToEndTests.TestUpdatingStoredProcedureWithRestMethods.verified.txt
index 0c20e9fc25..5a6a50d38e 100644
--- a/src/Cli.Tests/Snapshots/EndToEndTests.TestUpdatingStoredProcedureWithRestMethods.verified.txt
+++ b/src/Cli.Tests/Snapshots/EndToEndTests.TestUpdatingStoredProcedureWithRestMethods.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
AllowCredentials: false
diff --git a/src/Cli.Tests/Snapshots/EndToEndTests.TestUpdatingStoredProcedureWithRestMethodsAndGraphQLOperations.verified.txt b/src/Cli.Tests/Snapshots/EndToEndTests.TestUpdatingStoredProcedureWithRestMethodsAndGraphQLOperations.verified.txt
index 27b20753d3..540a1b5a1d 100644
--- a/src/Cli.Tests/Snapshots/EndToEndTests.TestUpdatingStoredProcedureWithRestMethodsAndGraphQLOperations.verified.txt
+++ b/src/Cli.Tests/Snapshots/EndToEndTests.TestUpdatingStoredProcedureWithRestMethodsAndGraphQLOperations.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
AllowCredentials: false
diff --git a/src/Cli.Tests/Snapshots/InitTests.CosmosDbNoSqlDatabase.verified.txt b/src/Cli.Tests/Snapshots/InitTests.CosmosDbNoSqlDatabase.verified.txt
index 2af3cbc907..b3f63dd336 100644
--- a/src/Cli.Tests/Snapshots/InitTests.CosmosDbNoSqlDatabase.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.CosmosDbNoSqlDatabase.verified.txt
@@ -17,6 +17,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
AllowCredentials: false
diff --git a/src/Cli.Tests/Snapshots/InitTests.CosmosDbPostgreSqlDatabase.verified.txt b/src/Cli.Tests/Snapshots/InitTests.CosmosDbPostgreSqlDatabase.verified.txt
index ca3b61588b..42e0ff5e2f 100644
--- a/src/Cli.Tests/Snapshots/InitTests.CosmosDbPostgreSqlDatabase.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.CosmosDbPostgreSqlDatabase.verified.txt
@@ -13,6 +13,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_171ea8114ff71814.verified.txt b/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_171ea8114ff71814.verified.txt
index 93190d1d9d..0af93023dc 100644
--- a/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_171ea8114ff71814.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_171ea8114ff71814.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
AllowCredentials: false
diff --git a/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_2df7a1794712f154.verified.txt b/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_2df7a1794712f154.verified.txt
index 5c52bc12c1..9e77b24d74 100644
--- a/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_2df7a1794712f154.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_2df7a1794712f154.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
AllowCredentials: false
diff --git a/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_59fe1a10aa78899d.verified.txt b/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_59fe1a10aa78899d.verified.txt
index 7b0a4674eb..32f72a7a54 100644
--- a/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_59fe1a10aa78899d.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_59fe1a10aa78899d.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
AllowCredentials: false
diff --git a/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_b95b637ea87f16a7.verified.txt b/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_b95b637ea87f16a7.verified.txt
index dc60d762cc..24416a0d02 100644
--- a/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_b95b637ea87f16a7.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_b95b637ea87f16a7.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
AllowCredentials: false
diff --git a/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_daacbd948b7ef72f.verified.txt b/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_daacbd948b7ef72f.verified.txt
index 7a67eca701..6c674a4772 100644
--- a/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_daacbd948b7ef72f.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_daacbd948b7ef72f.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
AllowCredentials: false
diff --git a/src/Cli.Tests/Snapshots/InitTests.GraphQLPathWithoutStartingSlashWillHaveItAdded.verified.txt b/src/Cli.Tests/Snapshots/InitTests.GraphQLPathWithoutStartingSlashWillHaveItAdded.verified.txt
index 8c2ffbbcac..b6aac13236 100644
--- a/src/Cli.Tests/Snapshots/InitTests.GraphQLPathWithoutStartingSlashWillHaveItAdded.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.GraphQLPathWithoutStartingSlashWillHaveItAdded.verified.txt
@@ -16,6 +16,10 @@
Path: /abc,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
AllowCredentials: false
diff --git a/src/Cli.Tests/Snapshots/InitTests.MsSQLDatabase.verified.txt b/src/Cli.Tests/Snapshots/InitTests.MsSQLDatabase.verified.txt
index da7937d1d9..8841c0f326 100644
--- a/src/Cli.Tests/Snapshots/InitTests.MsSQLDatabase.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.MsSQLDatabase.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.RestPathWithoutStartingSlashWillHaveItAdded.verified.txt b/src/Cli.Tests/Snapshots/InitTests.RestPathWithoutStartingSlashWillHaveItAdded.verified.txt
index ef8c7173d5..68e4d231fd 100644
--- a/src/Cli.Tests/Snapshots/InitTests.RestPathWithoutStartingSlashWillHaveItAdded.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.RestPathWithoutStartingSlashWillHaveItAdded.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
AllowCredentials: false
diff --git a/src/Cli.Tests/Snapshots/InitTests.TestInitializingConfigWithoutConnectionString.verified.txt b/src/Cli.Tests/Snapshots/InitTests.TestInitializingConfigWithoutConnectionString.verified.txt
index 72f66f82c9..3c281ad6aa 100644
--- a/src/Cli.Tests/Snapshots/InitTests.TestInitializingConfigWithoutConnectionString.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.TestInitializingConfigWithoutConnectionString.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.TestSpecialCharactersInConnectionString.verified.txt b/src/Cli.Tests/Snapshots/InitTests.TestSpecialCharactersInConnectionString.verified.txt
index 7b0a4674eb..32f72a7a54 100644
--- a/src/Cli.Tests/Snapshots/InitTests.TestSpecialCharactersInConnectionString.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.TestSpecialCharactersInConnectionString.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
AllowCredentials: false
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_0546bef37027a950.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_0546bef37027a950.verified.txt
index cbaaa45754..888466ab4a 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_0546bef37027a950.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_0546bef37027a950.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_0ac567dd32a2e8f5.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_0ac567dd32a2e8f5.verified.txt
index da7937d1d9..8841c0f326 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_0ac567dd32a2e8f5.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_0ac567dd32a2e8f5.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_0c06949221514e77.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_0c06949221514e77.verified.txt
index 62fc407842..d56e05c483 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_0c06949221514e77.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_0c06949221514e77.verified.txt
@@ -21,6 +21,10 @@
}
}
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_18667ab7db033e9d.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_18667ab7db033e9d.verified.txt
index 3285438ab7..bc31484242 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_18667ab7db033e9d.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_18667ab7db033e9d.verified.txt
@@ -13,6 +13,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_2f42f44c328eb020.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_2f42f44c328eb020.verified.txt
index cbaaa45754..888466ab4a 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_2f42f44c328eb020.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_2f42f44c328eb020.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_3243d3f3441fdcc1.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_3243d3f3441fdcc1.verified.txt
index 3285438ab7..bc31484242 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_3243d3f3441fdcc1.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_3243d3f3441fdcc1.verified.txt
@@ -13,6 +13,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_53350b8b47df2112.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_53350b8b47df2112.verified.txt
index a43e68277c..48f5e7a7c9 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_53350b8b47df2112.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_53350b8b47df2112.verified.txt
@@ -13,6 +13,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_6584e0ec46b8a11d.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_6584e0ec46b8a11d.verified.txt
index 9740a85a77..8fa9677f1d 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_6584e0ec46b8a11d.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_6584e0ec46b8a11d.verified.txt
@@ -17,6 +17,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_81cc88db3d4eecfb.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_81cc88db3d4eecfb.verified.txt
index be47d537b2..e3108801f5 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_81cc88db3d4eecfb.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_81cc88db3d4eecfb.verified.txt
@@ -21,6 +21,10 @@
}
}
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_8ea187616dbb5577.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_8ea187616dbb5577.verified.txt
index 673c21dae4..59f6636fb2 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_8ea187616dbb5577.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_8ea187616dbb5577.verified.txt
@@ -13,6 +13,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_905845c29560a3ef.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_905845c29560a3ef.verified.txt
index cbaaa45754..888466ab4a 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_905845c29560a3ef.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_905845c29560a3ef.verified.txt
@@ -16,6 +16,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_b2fd24fab5b80917.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_b2fd24fab5b80917.verified.txt
index 9740a85a77..8fa9677f1d 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_b2fd24fab5b80917.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_b2fd24fab5b80917.verified.txt
@@ -17,6 +17,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_bd7cd088755287c9.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_bd7cd088755287c9.verified.txt
index 9740a85a77..8fa9677f1d 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_bd7cd088755287c9.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_bd7cd088755287c9.verified.txt
@@ -17,6 +17,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_d2eccba2f836b380.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_d2eccba2f836b380.verified.txt
index a43e68277c..48f5e7a7c9 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_d2eccba2f836b380.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_d2eccba2f836b380.verified.txt
@@ -13,6 +13,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_d463eed7fe5e4bbe.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_d463eed7fe5e4bbe.verified.txt
index 673c21dae4..59f6636fb2 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_d463eed7fe5e4bbe.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_d463eed7fe5e4bbe.verified.txt
@@ -13,6 +13,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_d5520dd5c33f7b8d.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_d5520dd5c33f7b8d.verified.txt
index a43e68277c..48f5e7a7c9 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_d5520dd5c33f7b8d.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_d5520dd5c33f7b8d.verified.txt
@@ -13,6 +13,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_eab4a6010e602b59.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_eab4a6010e602b59.verified.txt
index 3285438ab7..bc31484242 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_eab4a6010e602b59.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_eab4a6010e602b59.verified.txt
@@ -13,6 +13,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_ecaa688829b4030e.verified.txt b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_ecaa688829b4030e.verified.txt
index 673c21dae4..59f6636fb2 100644
--- a/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_ecaa688829b4030e.verified.txt
+++ b/src/Cli.Tests/Snapshots/InitTests.VerifyCorrectConfigGenerationWithMultipleMutationOptions_ecaa688829b4030e.verified.txt
@@ -13,6 +13,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Cli.Tests/UpdateEntityTests.cs b/src/Cli.Tests/UpdateEntityTests.cs
index a500858c60..663334c5e8 100644
--- a/src/Cli.Tests/UpdateEntityTests.cs
+++ b/src/Cli.Tests/UpdateEntityTests.cs
@@ -1004,7 +1004,7 @@ public void TestVerifyCanUpdateRelationshipInvalidOptions(string db, string card
RuntimeConfig runtimeConfig = new(
Schema: "schema",
DataSource: new DataSource(EnumExtensions.Deserialize(db), "", new()),
- Runtime: new(Rest: new(), GraphQL: new(), Host: new(null, null)),
+ Runtime: new(Rest: new(), GraphQL: new(), Mcp: new(), Host: new(null, null)),
Entities: new(new Dictionary())
);
@@ -1056,7 +1056,7 @@ public void EnsureFailure_AddRelationshipToEntityWithDisabledGraphQL()
RuntimeConfig runtimeConfig = new(
Schema: "schema",
DataSource: new DataSource(DatabaseType.MSSQL, "", new()),
- Runtime: new(Rest: new(), GraphQL: new(), Host: new(null, null)),
+ Runtime: new(Rest: new(), GraphQL: new(), Mcp: new(), Host: new(null, null)),
Entities: new(entityMap)
);
diff --git a/src/Cli/Commands/ConfigureOptions.cs b/src/Cli/Commands/ConfigureOptions.cs
index 4f61b2007b..60cb12c3f8 100644
--- a/src/Cli/Commands/ConfigureOptions.cs
+++ b/src/Cli/Commands/ConfigureOptions.cs
@@ -36,6 +36,15 @@ public ConfigureOptions(
bool? runtimeRestEnabled = null,
string? runtimeRestPath = null,
bool? runtimeRestRequestBodyStrict = null,
+ bool? runtimeMcpEnabled = null,
+ string? runtimeMcpPath = null,
+ bool? runtimeMcpDmlToolsEnabled = null,
+ bool? runtimeMcpDmlToolsDescribeEntitiesEnabled = null,
+ bool? runtimeMcpDmlToolsCreateRecordEnabled = null,
+ bool? runtimeMcpDmlToolsReadRecordsEnabled = null,
+ bool? runtimeMcpDmlToolsUpdateRecordEnabled = null,
+ bool? runtimeMcpDmlToolsDeleteRecordEnabled = null,
+ bool? runtimeMcpDmlToolsExecuteEntityEnabled = null,
bool? runtimeCacheEnabled = null,
int? runtimeCacheTtl = null,
HostMode? runtimeHostMode = null,
@@ -81,6 +90,16 @@ public ConfigureOptions(
RuntimeRestEnabled = runtimeRestEnabled;
RuntimeRestPath = runtimeRestPath;
RuntimeRestRequestBodyStrict = runtimeRestRequestBodyStrict;
+ // Mcp
+ RuntimeMcpEnabled = runtimeMcpEnabled;
+ RuntimeMcpPath = runtimeMcpPath;
+ RuntimeMcpDmlToolsEnabled = runtimeMcpDmlToolsEnabled;
+ RuntimeMcpDmlToolsDescribeEntitiesEnabled = runtimeMcpDmlToolsDescribeEntitiesEnabled;
+ RuntimeMcpDmlToolsCreateRecordEnabled = runtimeMcpDmlToolsCreateRecordEnabled;
+ RuntimeMcpDmlToolsReadRecordsEnabled = runtimeMcpDmlToolsReadRecordsEnabled;
+ RuntimeMcpDmlToolsUpdateRecordEnabled = runtimeMcpDmlToolsUpdateRecordEnabled;
+ RuntimeMcpDmlToolsDeleteRecordEnabled = runtimeMcpDmlToolsDeleteRecordEnabled;
+ RuntimeMcpDmlToolsExecuteEntityEnabled = runtimeMcpDmlToolsExecuteEntityEnabled;
// Cache
RuntimeCacheEnabled = runtimeCacheEnabled;
RuntimeCacheTTL = runtimeCacheTtl;
@@ -155,6 +174,33 @@ public ConfigureOptions(
[Option("runtime.rest.request-body-strict", Required = false, HelpText = "Prohibit extraneous REST request body fields. Default: true (boolean).")]
public bool? RuntimeRestRequestBodyStrict { get; }
+ [Option("runtime.mcp.enabled", Required = false, HelpText = "Enable DAB's MCP endpoint. Default: true (boolean).")]
+ public bool? RuntimeMcpEnabled { get; }
+
+ [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.dml-tools.enabled", Required = false, HelpText = "Enable DAB's MCP DML tools endpoint. Default: true (boolean).")]
+ public bool? RuntimeMcpDmlToolsEnabled { get; }
+
+ [Option("runtime.mcp.dml-tools.describe-entities.enabled", Required = false, HelpText = "Enable DAB's MCP describe entities tool. Default: true (boolean).")]
+ public bool? RuntimeMcpDmlToolsDescribeEntitiesEnabled { get; }
+
+ [Option("runtime.mcp.dml-tools.create-record.enabled", Required = false, HelpText = "Enable DAB's MCP create record tool. Default: true (boolean).")]
+ public bool? RuntimeMcpDmlToolsCreateRecordEnabled { get; }
+
+ [Option("runtime.mcp.dml-tools.read-records.enabled", Required = false, HelpText = "Enable DAB's MCP read record tool. Default: true (boolean).")]
+ public bool? RuntimeMcpDmlToolsReadRecordsEnabled { get; }
+
+ [Option("runtime.mcp.dml-tools.update-record.enabled", Required = false, HelpText = "Enable DAB's MCP update record tool. Default: true (boolean).")]
+ public bool? RuntimeMcpDmlToolsUpdateRecordEnabled { get; }
+
+ [Option("runtime.mcp.dml-tools.delete-record.enabled", Required = false, HelpText = "Enable DAB's MCP delete record tool. Default: true (boolean).")]
+ public bool? RuntimeMcpDmlToolsDeleteRecordEnabled { get; }
+
+ [Option("runtime.mcp.dml-tools.execute-entity.enabled", Required = false, HelpText = "Enable DAB's MCP execute entity tool. Default: true (boolean).")]
+ public bool? RuntimeMcpDmlToolsExecuteEntityEnabled { get; }
+
[Option("runtime.cache.enabled", Required = false, HelpText = "Enable DAB's cache globally. (You must also enable each entity's cache separately.). Default: false (boolean).")]
public bool? RuntimeCacheEnabled { get; }
diff --git a/src/Cli/Commands/InitOptions.cs b/src/Cli/Commands/InitOptions.cs
index 5d5608a200..91786d99ff 100644
--- a/src/Cli/Commands/InitOptions.cs
+++ b/src/Cli/Commands/InitOptions.cs
@@ -35,8 +35,11 @@ public InitOptions(
bool restDisabled = false,
string graphQLPath = GraphQLRuntimeOptions.DEFAULT_PATH,
bool graphqlDisabled = false,
+ string mcpPath = McpRuntimeOptions.DEFAULT_PATH,
+ bool mcpDisabled = false,
CliBool restEnabled = CliBool.None,
CliBool graphqlEnabled = CliBool.None,
+ CliBool mcpEnabled = CliBool.None,
CliBool restRequestBodyStrict = CliBool.None,
CliBool multipleCreateOperationEnabled = CliBool.None,
string? config = null)
@@ -58,8 +61,11 @@ public InitOptions(
RestDisabled = restDisabled;
GraphQLPath = graphQLPath;
GraphQLDisabled = graphqlDisabled;
+ McpPath = mcpPath;
+ McpDisabled = mcpDisabled;
RestEnabled = restEnabled;
GraphQLEnabled = graphqlEnabled;
+ McpEnabled = mcpEnabled;
RestRequestBodyStrict = restRequestBodyStrict;
MultipleCreateOperationEnabled = multipleCreateOperationEnabled;
}
@@ -112,12 +118,21 @@ public InitOptions(
[Option("graphql.disabled", Default = false, Required = false, HelpText = "Disables GraphQL endpoint for all entities.")]
public bool GraphQLDisabled { get; }
+ [Option("mcp.path", Default = McpRuntimeOptions.DEFAULT_PATH, Required = false, HelpText = "Specify the MCP endpoint's default prefix.")]
+ public string McpPath { get; }
+
+ [Option("mcp.disabled", Default = false, Required = false, HelpText = "Disables MCP endpoint for all entities.")]
+ public bool McpDisabled { get; }
+
[Option("rest.enabled", Required = false, HelpText = "(Default: true) Enables REST endpoint for all entities. Supported values: true, false.")]
public CliBool RestEnabled { get; }
[Option("graphql.enabled", Required = false, HelpText = "(Default: true) Enables GraphQL endpoint for all entities. Supported values: true, false.")]
public CliBool GraphQLEnabled { get; }
+ [Option("mcp.enabled", Required = false, HelpText = "(Default: true) Enables MCP endpoint for all entities. Supported values: true, false.")]
+ public CliBool McpEnabled { get; }
+
// Since the rest.request-body-strict option does not have a default value, it is required to specify a value for this option if it is
// included in the init command.
[Option("rest.request-body-strict", Required = false, HelpText = "(Default: true) Allow extraneous fields in the request body for REST.")]
diff --git a/src/Cli/ConfigGenerator.cs b/src/Cli/ConfigGenerator.cs
index c7027ff78c..886447b256 100644
--- a/src/Cli/ConfigGenerator.cs
+++ b/src/Cli/ConfigGenerator.cs
@@ -89,6 +89,7 @@ public static bool TryCreateRuntimeConfig(InitOptions options, FileSystemRuntime
DatabaseType dbType = options.DatabaseType;
string? restPath = options.RestPath;
string graphQLPath = options.GraphQLPath;
+ string mcpPath = options.McpPath;
string? runtimeBaseRoute = options.RuntimeBaseRoute;
Dictionary dbOptions = new();
@@ -108,9 +109,10 @@ public static bool TryCreateRuntimeConfig(InitOptions options, FileSystemRuntime
" We recommend that you use the --graphql.enabled option instead.");
}
- bool restEnabled, graphQLEnabled;
+ bool restEnabled, graphQLEnabled, mcpEnabled;
if (!TryDetermineIfApiIsEnabled(options.RestDisabled, options.RestEnabled, ApiType.REST, out restEnabled) ||
- !TryDetermineIfApiIsEnabled(options.GraphQLDisabled, options.GraphQLEnabled, ApiType.GraphQL, out graphQLEnabled))
+ !TryDetermineIfApiIsEnabled(options.GraphQLDisabled, options.GraphQLEnabled, ApiType.GraphQL, out graphQLEnabled) ||
+ !TryDetermineIfMcpIsEnabled(options.McpEnabled, out mcpEnabled))
{
return false;
}
@@ -262,6 +264,7 @@ public static bool TryCreateRuntimeConfig(InitOptions options, FileSystemRuntime
Runtime: new(
Rest: new(restEnabled, restPath ?? RestRuntimeOptions.DEFAULT_PATH, options.RestRequestBodyStrict is CliBool.False ? false : true),
GraphQL: new(Enabled: graphQLEnabled, Path: graphQLPath, MultipleMutationOptions: multipleMutationOptions),
+ Mcp: new(mcpEnabled, mcpPath ?? McpRuntimeOptions.DEFAULT_PATH),
Host: new(
Cors: new(options.CorsOrigin?.ToArray() ?? Array.Empty()),
Authentication: new(
@@ -314,6 +317,17 @@ private static bool TryDetermineIfApiIsEnabled(bool apiDisabledOptionValue, CliB
return true;
}
+ ///
+ /// Helper method to determine if the mcp api is enabled or not based on the enabled/disabled options in the dab init command.
+ ///
+ /// True, if MCP is enabled
+ /// Out param isMcpEnabled
+ /// True if MCP is enabled
+ private static bool TryDetermineIfMcpIsEnabled(CliBool mcpEnabledOptionValue, out bool isMcpEnabled)
+ {
+ return TryDetermineIfApiIsEnabled(false, mcpEnabledOptionValue, ApiType.MCP, out isMcpEnabled);
+ }
+
///
/// Helper method to determine if the multiple create operation is enabled or not based on the inputs from dab init command.
///
@@ -744,6 +758,23 @@ private static bool TryUpdateConfiguredRuntimeOptions(
}
}
+ // MCP: Enabled and Path
+ if (options.RuntimeMcpEnabled != null ||
+ options.RuntimeMcpPath != null)
+ {
+ McpRuntimeOptions updatedMcpOptions = runtimeConfig?.Runtime?.Mcp ?? new();
+ bool status = TryUpdateConfiguredMcpValues(options, ref updatedMcpOptions);
+
+ if (status)
+ {
+ runtimeConfig = runtimeConfig! with { Runtime = runtimeConfig.Runtime! with { Mcp = updatedMcpOptions } };
+ }
+ else
+ {
+ return false;
+ }
+ }
+
// Cache: Enabled and TTL
if (options.RuntimeCacheEnabled != null ||
options.RuntimeCacheTTL != null)
@@ -944,6 +975,142 @@ private static bool TryUpdateConfiguredGraphQLValues(
}
}
+ ///
+ /// Attempts to update the Config parameters in the Mcp runtime settings based on the provided value.
+ /// Validates that any user-provided values are valid and then returns true if the updated Mcp options
+ /// need to be overwritten on the existing config parameters
+ ///
+ /// options.
+ /// updatedMcpOptions
+ /// True if the value needs to be updated in the runtime config, else false
+ private static bool TryUpdateConfiguredMcpValues(
+ ConfigureOptions options,
+ ref McpRuntimeOptions updatedMcpOptions)
+ {
+ object? updatedValue;
+
+ try
+ {
+ // Runtime.Mcp.Enabled
+ updatedValue = options?.RuntimeMcpEnabled;
+ if (updatedValue != null)
+ {
+ updatedMcpOptions = updatedMcpOptions! with { Enabled = (bool)updatedValue };
+ _logger.LogInformation("Updated RuntimeConfig with Runtime.Mcp.Enabled as '{updatedValue}'", updatedValue);
+ }
+
+ // Runtime.Mcp.Path
+ updatedValue = options?.RuntimeMcpPath;
+ if (updatedValue != null)
+ {
+ bool status = RuntimeConfigValidatorUtil.TryValidateUriComponent(uriComponent: (string)updatedValue, out string exceptionMessage);
+ if (status)
+ {
+ updatedMcpOptions = updatedMcpOptions! with { Path = (string)updatedValue };
+ _logger.LogInformation("Updated RuntimeConfig with Runtime.Mcp.Path as '{updatedValue}'", updatedValue);
+ }
+ else
+ {
+ _logger.LogError("Failed to update Runtime.Mcp.Path as '{updatedValue}' due to exception message: {exceptionMessage}", updatedValue, exceptionMessage);
+ return false;
+ }
+ }
+
+ // Handle DML tools configuration
+ bool hasToolUpdates = false;
+ DmlToolsConfig? currentDmlTools = updatedMcpOptions?.DmlTools;
+
+ // If setting all tools at once
+ updatedValue = options?.RuntimeMcpDmlToolsEnabled;
+ if (updatedValue != null)
+ {
+ updatedMcpOptions = updatedMcpOptions! with { DmlTools = DmlToolsConfig.FromBoolean((bool)updatedValue) };
+ _logger.LogInformation("Updated RuntimeConfig with Runtime.Mcp.Dml-Tools as '{updatedValue}'", updatedValue);
+ return true; // Return early since we're setting all tools at once
+ }
+
+ // Handle individual tool updates
+ bool? describeEntities = currentDmlTools?.DescribeEntities;
+ bool? createRecord = currentDmlTools?.CreateRecord;
+ bool? readRecord = currentDmlTools?.ReadRecords;
+ bool? updateRecord = currentDmlTools?.UpdateRecord;
+ bool? deleteRecord = currentDmlTools?.DeleteRecord;
+ bool? executeEntity = currentDmlTools?.ExecuteEntity;
+
+ updatedValue = options?.RuntimeMcpDmlToolsDescribeEntitiesEnabled;
+ if (updatedValue != null)
+ {
+ describeEntities = (bool)updatedValue;
+ hasToolUpdates = true;
+ _logger.LogInformation("Updated RuntimeConfig with runtime.mcp.dml-tools.describe-entities as '{updatedValue}'", updatedValue);
+ }
+
+ updatedValue = options?.RuntimeMcpDmlToolsCreateRecordEnabled;
+ if (updatedValue != null)
+ {
+ createRecord = (bool)updatedValue;
+ hasToolUpdates = true;
+ _logger.LogInformation("Updated RuntimeConfig with runtime.mcp.dml-tools.create-record as '{updatedValue}'", updatedValue);
+ }
+
+ updatedValue = options?.RuntimeMcpDmlToolsReadRecordsEnabled;
+ if (updatedValue != null)
+ {
+ readRecord = (bool)updatedValue;
+ hasToolUpdates = true;
+ _logger.LogInformation("Updated RuntimeConfig with runtime.mcp.dml-tools.read-records as '{updatedValue}'", updatedValue);
+ }
+
+ updatedValue = options?.RuntimeMcpDmlToolsUpdateRecordEnabled;
+ if (updatedValue != null)
+ {
+ updateRecord = (bool)updatedValue;
+ hasToolUpdates = true;
+ _logger.LogInformation("Updated RuntimeConfig with runtime.mcp.dml-tools.update-record as '{updatedValue}'", updatedValue);
+ }
+
+ updatedValue = options?.RuntimeMcpDmlToolsDeleteRecordEnabled;
+ if (updatedValue != null)
+ {
+ deleteRecord = (bool)updatedValue;
+ hasToolUpdates = true;
+ _logger.LogInformation("Updated RuntimeConfig with runtime.mcp.dml-tools.delete-record as '{updatedValue}'", updatedValue);
+ }
+
+ updatedValue = options?.RuntimeMcpDmlToolsExecuteEntityEnabled;
+ if (updatedValue != null)
+ {
+ executeEntity = (bool)updatedValue;
+ hasToolUpdates = true;
+ _logger.LogInformation("Updated RuntimeConfig with runtime.mcp.dml-tools.execute-entity as '{updatedValue}'", updatedValue);
+ }
+
+ if (hasToolUpdates)
+ {
+ updatedMcpOptions = updatedMcpOptions! with
+ {
+ DmlTools = new DmlToolsConfig
+ {
+ AllToolsEnabled = false,
+ DescribeEntities = describeEntities,
+ CreateRecord = createRecord,
+ ReadRecords = readRecord,
+ UpdateRecord = updateRecord,
+ DeleteRecord = deleteRecord,
+ ExecuteEntity = executeEntity
+ }
+ };
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError("Failed to update RuntimeConfig.Mcp with exception message: {exceptionMessage}.", ex.Message);
+ return false;
+ }
+ }
+
///
/// Attempts to update the Config parameters in the Cache runtime settings based on the provided value.
/// Validates user-provided parameters and then returns true if the updated Cache options
@@ -2229,7 +2396,7 @@ private static bool TryUpdateConfiguredAzureKeyVaultOptions(
{
if (options.AzureKeyVaultRetryPolicyMaxCount.Value < 1)
{
- _logger.LogError("Failed to update azure-key-vault.retry-policy.max-count. Value must be at least 1.");
+ _logger.LogError("Failed to update configuration with runtime.azure-key-vault.retry-policy.max-count. Value must be a positive integer greater than 0.");
return false;
}
@@ -2244,7 +2411,7 @@ private static bool TryUpdateConfiguredAzureKeyVaultOptions(
{
if (options.AzureKeyVaultRetryPolicyDelaySeconds.Value < 1)
{
- _logger.LogError("Failed to update azure-key-vault.retry-policy.delay-seconds. Value must be at least 1.");
+ _logger.LogError("Failed to update configuration with runtime.azure-key-vault.retry-policy.delay-seconds. Value must be a positive integer greater than 0.");
return false;
}
@@ -2259,7 +2426,7 @@ private static bool TryUpdateConfiguredAzureKeyVaultOptions(
{
if (options.AzureKeyVaultRetryPolicyMaxDelaySeconds.Value < 1)
{
- _logger.LogError("Failed to update azure-key-vault.retry-policy.max-delay-seconds. Value must be at least 1.");
+ _logger.LogError("Failed to update configuration with runtime.azure-key-vault.retry-policy.max-delay-seconds. Value must be a positive integer greater than 0.");
return false;
}
@@ -2274,7 +2441,7 @@ private static bool TryUpdateConfiguredAzureKeyVaultOptions(
{
if (options.AzureKeyVaultRetryPolicyNetworkTimeoutSeconds.Value < 1)
{
- _logger.LogError("Failed to update azure-key-vault.retry-policy.network-timeout-seconds. Value must be at least 1.");
+ _logger.LogError("Failed to update configuration with runtime.azure-key-vault.retry-policy.network-timeout-seconds. Value must be a positive integer greater than 0.");
return false;
}
diff --git a/src/Config/Converters/DmlToolsConfigConverter.cs b/src/Config/Converters/DmlToolsConfigConverter.cs
new file mode 100644
index 0000000000..9acef0f9b2
--- /dev/null
+++ b/src/Config/Converters/DmlToolsConfigConverter.cs
@@ -0,0 +1,188 @@
+// 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;
+
+///
+/// JSON converter for DmlToolsConfig that handles both boolean and object formats.
+///
+internal class DmlToolsConfigConverter : JsonConverter
+{
+ ///
+ /// Reads DmlToolsConfig from JSON which can be either:
+ /// - A boolean: all tools are enabled/disabled
+ /// - An object: individual tool settings (unspecified tools default to true)
+ /// - Null/undefined: defaults to all tools enabled (true)
+ ///
+ public override DmlToolsConfig? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ // Handle null
+ if (reader.TokenType is JsonTokenType.Null)
+ {
+ // Return default config with all tools enabled
+ return DmlToolsConfig.Default;
+ }
+
+ // Handle boolean format: "dml-tools": true/false
+ if (reader.TokenType is JsonTokenType.True || reader.TokenType is JsonTokenType.False)
+ {
+ bool enabled = reader.GetBoolean();
+ return DmlToolsConfig.FromBoolean(enabled);
+ }
+
+ // Handle object format
+ if (reader.TokenType is JsonTokenType.StartObject)
+ {
+ // When using object format, unspecified tools default to true
+ bool? describeEntities = null;
+ bool? createRecord = null;
+ bool? readRecords = null;
+ bool? updateRecord = null;
+ bool? deleteRecord = null;
+ bool? executeEntity = null;
+
+ while (reader.Read())
+ {
+ if (reader.TokenType is JsonTokenType.EndObject)
+ {
+ break;
+ }
+
+ if (reader.TokenType is JsonTokenType.PropertyName)
+ {
+ string? property = reader.GetString();
+ reader.Read();
+
+ // Handle the property value
+ if (reader.TokenType is JsonTokenType.True || reader.TokenType is JsonTokenType.False)
+ {
+ bool value = reader.GetBoolean();
+
+ switch (property?.ToLowerInvariant())
+ {
+ case "describe-entities":
+ describeEntities = value;
+ break;
+ case "create-record":
+ createRecord = value;
+ break;
+ case "read-records":
+ readRecords = value;
+ break;
+ case "update-record":
+ updateRecord = value;
+ break;
+ case "delete-record":
+ deleteRecord = value;
+ break;
+ case "execute-entity":
+ executeEntity = value;
+ break;
+ default:
+ // Skip unknown properties
+ break;
+ }
+ }
+ else
+ {
+ // Error on non-boolean values for known properties
+ if (property?.ToLowerInvariant() is "describe-entities" or "create-record"
+ or "read-records" or "update-record" or "delete-record" or "execute-entity")
+ {
+ throw new JsonException($"Property '{property}' must be a boolean value.");
+ }
+
+ // Skip unknown properties
+ reader.Skip();
+ }
+ }
+ }
+
+ // Create the config with specified values
+ // Unspecified values (null) will default to true in the DmlToolsConfig constructor
+ return new DmlToolsConfig(
+ allToolsEnabled: null,
+ describeEntities: describeEntities,
+ createRecord: createRecord,
+ readRecords: readRecords,
+ updateRecord: updateRecord,
+ deleteRecord: deleteRecord,
+ executeEntity: executeEntity);
+ }
+
+ // For any other unexpected token type, return default (all enabled)
+ return DmlToolsConfig.Default;
+ }
+
+ ///
+ /// Writes DmlToolsConfig to JSON.
+ /// - If all tools have the same value, writes as boolean
+ /// - Otherwise writes as object with only user-provided properties
+ ///
+ public override void Write(Utf8JsonWriter writer, DmlToolsConfig? value, JsonSerializerOptions options)
+ {
+ if (value is null)
+ {
+ return;
+ }
+
+ // Check if any individual settings were provided by the user
+ bool hasIndividualSettings = value.UserProvidedDescribeEntities ||
+ value.UserProvidedCreateRecord ||
+ value.UserProvidedReadRecords ||
+ value.UserProvidedUpdateRecord ||
+ value.UserProvidedDeleteRecord ||
+ value.UserProvidedExecuteEntity;
+
+ // Only write the boolean value if it's provided by user
+ // This prevents writing "dml-tools": true when it's the default
+ if (!hasIndividualSettings && value.UserProvidedAllToolsEnabled)
+ {
+ writer.WritePropertyName("dml-tools");
+ writer.WriteBooleanValue(value.AllToolsEnabled);
+ }
+ else
+ {
+ writer.WritePropertyName("dml-tools");
+
+ // Write as object with only user-provided properties
+ writer.WriteStartObject();
+
+ if (value.UserProvidedDescribeEntities && value.DescribeEntities.HasValue)
+ {
+ writer.WriteBoolean("describe-entities", value.DescribeEntities.Value);
+ }
+
+ if (value.UserProvidedCreateRecord && value.CreateRecord.HasValue)
+ {
+ writer.WriteBoolean("create-record", value.CreateRecord.Value);
+ }
+
+ if (value.UserProvidedReadRecords && value.ReadRecords.HasValue)
+ {
+ writer.WriteBoolean("read-records", value.ReadRecords.Value);
+ }
+
+ if (value.UserProvidedUpdateRecord && value.UpdateRecord.HasValue)
+ {
+ writer.WriteBoolean("update-record", value.UpdateRecord.Value);
+ }
+
+ if (value.UserProvidedDeleteRecord && value.DeleteRecord.HasValue)
+ {
+ writer.WriteBoolean("delete-record", value.DeleteRecord.Value);
+ }
+
+ if (value.UserProvidedExecuteEntity && value.ExecuteEntity.HasValue)
+ {
+ writer.WriteBoolean("execute-entity", value.ExecuteEntity.Value);
+ }
+
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/Config/Converters/McpRuntimeOptionsConverterFactory.cs b/src/Config/Converters/McpRuntimeOptionsConverterFactory.cs
new file mode 100644
index 0000000000..db9acfa603
--- /dev/null
+++ b/src/Config/Converters/McpRuntimeOptionsConverterFactory.cs
@@ -0,0 +1,140 @@
+// 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;
+
+///
+/// JSON converter factory for McpRuntimeOptions that handles both boolean and object formats.
+///
+internal class McpRuntimeOptionsConverterFactory : JsonConverterFactory
+{
+ // Determines whether to replace environment variable with its
+ // value or not while deserializing.
+ private bool _replaceEnvVar;
+
+ ///
+ public override bool CanConvert(Type typeToConvert)
+ {
+ return typeToConvert.IsAssignableTo(typeof(McpRuntimeOptions));
+ }
+
+ ///
+ public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new McpRuntimeOptionsConverter(_replaceEnvVar);
+ }
+
+ internal McpRuntimeOptionsConverterFactory(bool replaceEnvVar)
+ {
+ _replaceEnvVar = replaceEnvVar;
+ }
+
+ private class McpRuntimeOptionsConverter : JsonConverter
+ {
+ // Determines whether to replace environment variable with its
+ // value or not while deserializing.
+ private bool _replaceEnvVar;
+
+ /// Whether to replace environment variable with its
+ /// value or not while deserializing.
+ internal McpRuntimeOptionsConverter(bool replaceEnvVar)
+ {
+ _replaceEnvVar = replaceEnvVar;
+ }
+
+ ///
+ /// Defines how DAB reads MCP options and defines which values are
+ /// used to instantiate McpRuntimeOptions.
+ ///
+ /// Thrown when improperly formatted MCP options are provided.
+ public override McpRuntimeOptions? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType == JsonTokenType.True || reader.TokenType == JsonTokenType.False)
+ {
+ return new McpRuntimeOptions(Enabled: reader.GetBoolean());
+ }
+
+ if (reader.TokenType is JsonTokenType.StartObject)
+ {
+ DmlToolsConfigConverter dmlToolsConfigConverter = new();
+
+ bool enabled = true;
+ string? path = null;
+ DmlToolsConfig? dmlTools = null;
+
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.EndObject)
+ {
+ return new McpRuntimeOptions(enabled, path, dmlTools);
+ }
+
+ string? propertyName = reader.GetString();
+
+ reader.Read();
+ switch (propertyName)
+ {
+ case "enabled":
+ if (reader.TokenType is not JsonTokenType.Null)
+ {
+ enabled = reader.GetBoolean();
+ }
+
+ break;
+
+ case "path":
+ if (reader.TokenType is not JsonTokenType.Null)
+ {
+ path = reader.DeserializeString(_replaceEnvVar);
+ }
+
+ break;
+
+ case "dml-tools":
+ dmlTools = dmlToolsConfigConverter.Read(ref reader, typeToConvert, options);
+ break;
+
+ default:
+ throw new JsonException($"Unexpected property {propertyName}");
+ }
+ }
+ }
+
+ throw new JsonException("Failed to read the MCP Options");
+ }
+
+ ///
+ /// When writing the McpRuntimeOptions back to a JSON file, only write the properties
+ /// if they are user provided. This avoids polluting the written JSON file with properties
+ /// the user most likely omitted when writing the original DAB runtime config file.
+ /// This Write operation is only used when a RuntimeConfig object is serialized to JSON.
+ ///
+ public override void Write(Utf8JsonWriter writer, McpRuntimeOptions value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ writer.WriteBoolean("enabled", value.Enabled);
+
+ if (value?.UserProvidedPath is true)
+ {
+ writer.WritePropertyName("path");
+ JsonSerializer.Serialize(writer, value.Path, options);
+ }
+
+ // Only write the boolean value if it's not the default (true)
+ // This prevents writing "dml-tools": true when it's the default
+ if (value?.DmlTools is not null)
+ {
+ DmlToolsConfigConverter dmlToolsOptionsConverter = options.GetConverter(typeof(DmlToolsConfig)) as DmlToolsConfigConverter ??
+ throw new JsonException("Failed to get mcp.dml-tools options converter");
+
+ dmlToolsOptionsConverter.Write(writer, value.DmlTools, options);
+ }
+
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/Config/DataApiBuilderException.cs b/src/Config/DataApiBuilderException.cs
index 18b0395541..b7696c4deb 100644
--- a/src/Config/DataApiBuilderException.cs
+++ b/src/Config/DataApiBuilderException.cs
@@ -105,6 +105,10 @@ public enum SubStatusCodes
///
GlobalRestEndpointDisabled,
///
+ /// Global MCP endpoint disabled in runtime configuration.
+ ///
+ GlobalMcpEndpointDisabled,
+ ///
/// DataSource not found for multiple db scenario.
///
DataSourceNotFound,
diff --git a/src/Config/ObjectModel/ApiType.cs b/src/Config/ObjectModel/ApiType.cs
index 5583e67098..fb57fe2859 100644
--- a/src/Config/ObjectModel/ApiType.cs
+++ b/src/Config/ObjectModel/ApiType.cs
@@ -10,6 +10,7 @@ public enum ApiType
{
REST,
GraphQL,
+ MCP,
// This is required to indicate features common between all APIs.
All
}
diff --git a/src/Config/ObjectModel/DmlToolsConfig.cs b/src/Config/ObjectModel/DmlToolsConfig.cs
new file mode 100644
index 0000000000..c14f8e49ed
--- /dev/null
+++ b/src/Config/ObjectModel/DmlToolsConfig.cs
@@ -0,0 +1,188 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json.Serialization;
+
+namespace Azure.DataApiBuilder.Config.ObjectModel;
+
+///
+/// DML Tools configuration that can be either a boolean or object with individual tool settings
+///
+public record DmlToolsConfig
+{
+ ///
+ /// Default value for all tools when not specified
+ ///
+ public const bool DEFAULT_ENABLED = true;
+
+ ///
+ /// Indicates if all tools are enabled/disabled uniformly
+ ///
+ public bool AllToolsEnabled { get; init; }
+
+ ///
+ /// Whether describe-entities tool is enabled
+ ///
+ public bool? DescribeEntities { get; init; }
+
+ ///
+ /// Whether create-record tool is enabled
+ ///
+ public bool? CreateRecord { get; init; }
+
+ ///
+ /// Whether read-records tool is enabled
+ ///
+ public bool? ReadRecords { get; init; }
+
+ ///
+ /// Whether update-record tool is enabled
+ ///
+ public bool? UpdateRecord { get; init; }
+
+ ///
+ /// Whether delete-record tool is enabled
+ ///
+ public bool? DeleteRecord { get; init; }
+
+ ///
+ /// Whether execute-entity tool is enabled
+ ///
+ public bool? ExecuteEntity { get; init; }
+
+ [JsonConstructor]
+ public DmlToolsConfig(
+ bool? allToolsEnabled = null,
+ bool? describeEntities = null,
+ bool? createRecord = null,
+ bool? readRecords = null,
+ bool? updateRecord = null,
+ bool? deleteRecord = null,
+ bool? executeEntity = null)
+ {
+ if (allToolsEnabled is not null)
+ {
+ AllToolsEnabled = allToolsEnabled.Value;
+ UserProvidedAllToolsEnabled = true;
+ }
+ else
+ {
+ AllToolsEnabled = DEFAULT_ENABLED;
+ }
+
+ if (describeEntities is not null)
+ {
+ DescribeEntities = describeEntities;
+ UserProvidedDescribeEntities = true;
+ }
+
+ if (createRecord is not null)
+ {
+ CreateRecord = createRecord;
+ UserProvidedCreateRecord = true;
+ }
+
+ if (readRecords is not null)
+ {
+ ReadRecords = readRecords;
+ UserProvidedReadRecords = true;
+ }
+
+ if (updateRecord is not null)
+ {
+ UpdateRecord = updateRecord;
+ UserProvidedUpdateRecord = true;
+ }
+
+ if (deleteRecord is not null)
+ {
+ DeleteRecord = deleteRecord;
+ UserProvidedDeleteRecord = true;
+ }
+
+ if (executeEntity is not null)
+ {
+ ExecuteEntity = executeEntity;
+ UserProvidedExecuteEntity = true;
+ }
+ }
+
+ ///
+ /// Creates a DmlToolsConfig with all tools set to the same state
+ ///
+ public static DmlToolsConfig FromBoolean(bool enabled)
+ {
+ return new DmlToolsConfig
+ {
+ AllToolsEnabled = enabled,
+ DescribeEntities = null,
+ CreateRecord = null,
+ ReadRecords = null,
+ UpdateRecord = null,
+ DeleteRecord = null,
+ ExecuteEntity = null
+ };
+ }
+
+ ///
+ /// Creates a default DmlToolsConfig with all tools enabled
+ ///
+ public static DmlToolsConfig Default => FromBoolean(DEFAULT_ENABLED);
+
+ ///
+ /// Flag which informs CLI and JSON serializer whether to write all-tools-enabled
+ /// property/value to the runtime config file.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
+ [MemberNotNullWhen(true, nameof(AllToolsEnabled))]
+ public bool UserProvidedAllToolsEnabled { get; init; } = false;
+
+ ///
+ /// Flag which informs CLI and JSON serializer whether to write describe-entities
+ /// property/value to the runtime config file.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
+ [MemberNotNullWhen(true, nameof(DescribeEntities))]
+ public bool UserProvidedDescribeEntities { get; init; } = false;
+
+ ///
+ /// Flag which informs CLI and JSON serializer whether to write create-record
+ /// property/value to the runtime config file.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
+ [MemberNotNullWhen(true, nameof(CreateRecord))]
+ public bool UserProvidedCreateRecord { get; init; } = false;
+
+ ///
+ /// Flag which informs CLI and JSON serializer whether to write read-records
+ /// property/value to the runtime config file.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
+ [MemberNotNullWhen(true, nameof(ReadRecords))]
+ public bool UserProvidedReadRecords { get; init; } = false;
+
+ ///
+ /// Flag which informs CLI and JSON serializer whether to write update-record
+ /// property/value to the runtime config file.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
+ [MemberNotNullWhen(true, nameof(UpdateRecord))]
+ public bool UserProvidedUpdateRecord { get; init; } = false;
+
+ ///
+ /// Flag which informs CLI and JSON serializer whether to write delete-record
+ /// property/value to the runtime config file.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
+ [MemberNotNullWhen(true, nameof(DeleteRecord))]
+ public bool UserProvidedDeleteRecord { get; init; } = false;
+
+ ///
+ /// Flag which informs CLI and JSON serializer whether to write execute-entity
+ /// property/value to the runtime config file.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
+ [MemberNotNullWhen(true, nameof(ExecuteEntity))]
+ public bool UserProvidedExecuteEntity { get; init; } = false;
+}
diff --git a/src/Config/ObjectModel/McpRuntimeOptions.cs b/src/Config/ObjectModel/McpRuntimeOptions.cs
new file mode 100644
index 0000000000..73d695ee4a
--- /dev/null
+++ b/src/Config/ObjectModel/McpRuntimeOptions.cs
@@ -0,0 +1,63 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json.Serialization;
+using Azure.DataApiBuilder.Config.Converters;
+
+namespace Azure.DataApiBuilder.Config.ObjectModel;
+
+public record McpRuntimeOptions
+{
+ public const string DEFAULT_PATH = "/mcp";
+
+ ///
+ /// Whether MCP endpoints are enabled
+ ///
+ [JsonPropertyName("enabled")]
+ public bool Enabled { get; init; } = true;
+
+ ///
+ /// The path where MCP endpoints will be exposed
+ ///
+ [JsonPropertyName("path")]
+ public string Path { get; init; } = DEFAULT_PATH;
+
+ ///
+ /// Configuration for DML tools
+ ///
+ [JsonPropertyName("dml-tools")]
+ [JsonConverter(typeof(DmlToolsConfigConverter))]
+ public DmlToolsConfig? DmlTools { get; init; }
+
+ [JsonConstructor]
+ public McpRuntimeOptions(
+ bool Enabled = true,
+ string? Path = null,
+ DmlToolsConfig? DmlTools = null)
+ {
+ this.Enabled = Enabled;
+
+ if (Path is not null)
+ {
+ this.Path = Path;
+ UserProvidedPath = true;
+ }
+ else
+ {
+ this.Path = DEFAULT_PATH;
+ }
+
+ this.DmlTools = DmlTools;
+ }
+
+ ///
+ /// Flag which informs CLI and JSON serializer whether to write path
+ /// property and value to the runtime config file.
+ /// When user doesn't provide the path property/value, which signals DAB to use the default,
+ /// the DAB CLI should not write the default value to a serialized config.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
+ [MemberNotNullWhen(true, nameof(Enabled))]
+ public bool UserProvidedPath { get; init; } = false;
+}
diff --git a/src/Config/ObjectModel/RuntimeConfig.cs b/src/Config/ObjectModel/RuntimeConfig.cs
index 7cf8159952..a450e1265c 100644
--- a/src/Config/ObjectModel/RuntimeConfig.cs
+++ b/src/Config/ObjectModel/RuntimeConfig.cs
@@ -72,6 +72,15 @@ Runtime.Rest is null ||
Runtime.Rest.Enabled) &&
DataSource.DatabaseType != DatabaseType.CosmosDB_NoSQL;
+ ///
+ /// Retrieves the value of runtime.mcp.enabled property if present, default is true.
+ ///
+ [JsonIgnore]
+ public bool IsMcpEnabled =>
+ Runtime is null ||
+ Runtime.Mcp is null ||
+ Runtime.Mcp.Enabled;
+
[JsonIgnore]
public bool IsHealthEnabled =>
Runtime is null ||
@@ -127,6 +136,25 @@ public string GraphQLPath
}
}
+ ///
+ /// The path at which MCP API is available
+ ///
+ [JsonIgnore]
+ public string McpPath
+ {
+ get
+ {
+ if (Runtime is null || Runtime.Mcp is null || Runtime.Mcp.Path is null)
+ {
+ return McpRuntimeOptions.DEFAULT_PATH;
+ }
+ else
+ {
+ return Runtime.Mcp.Path;
+ }
+ }
+ }
+
///
/// Indicates whether introspection is allowed or not.
///
@@ -707,4 +735,10 @@ public LogLevel GetConfiguredLogLevel(string loggerFilter = "")
return LogLevel.Error;
}
+
+ ///
+ /// Gets the MCP DML tools configuration
+ ///
+ [JsonIgnore]
+ public DmlToolsConfig? McpDmlTools => Runtime?.Mcp?.DmlTools;
}
diff --git a/src/Config/ObjectModel/RuntimeOptions.cs b/src/Config/ObjectModel/RuntimeOptions.cs
index 8e05df4b62..6f6c046651 100644
--- a/src/Config/ObjectModel/RuntimeOptions.cs
+++ b/src/Config/ObjectModel/RuntimeOptions.cs
@@ -10,6 +10,7 @@ public record RuntimeOptions
{
public RestRuntimeOptions? Rest { get; init; }
public GraphQLRuntimeOptions? GraphQL { get; init; }
+ public McpRuntimeOptions? Mcp { get; init; }
public HostOptions? Host { get; set; }
public string? BaseRoute { get; init; }
public TelemetryOptions? Telemetry { get; init; }
@@ -21,6 +22,7 @@ public record RuntimeOptions
public RuntimeOptions(
RestRuntimeOptions? Rest,
GraphQLRuntimeOptions? GraphQL,
+ McpRuntimeOptions? Mcp,
HostOptions? Host,
string? BaseRoute = null,
TelemetryOptions? Telemetry = null,
@@ -30,6 +32,7 @@ public RuntimeOptions(
{
this.Rest = Rest;
this.GraphQL = GraphQL;
+ this.Mcp = Mcp;
this.Host = Host;
this.BaseRoute = BaseRoute;
this.Telemetry = Telemetry;
@@ -60,6 +63,12 @@ GraphQL is null ||
GraphQL?.Enabled is null ||
GraphQL?.Enabled is true;
+ [JsonIgnore]
+ public bool IsMcpEnabled =>
+ Mcp is null ||
+ Mcp?.Enabled is null ||
+ Mcp?.Enabled is true;
+
[JsonIgnore]
public bool IsHealthCheckEnabled =>
Health is null ||
diff --git a/src/Config/RuntimeConfigLoader.cs b/src/Config/RuntimeConfigLoader.cs
index 4a220af0ea..f78c32ebc1 100644
--- a/src/Config/RuntimeConfigLoader.cs
+++ b/src/Config/RuntimeConfigLoader.cs
@@ -246,6 +246,8 @@ public static JsonSerializerOptions GetSerializationOptions(
options.Converters.Add(new EntityHealthOptionsConvertorFactory());
options.Converters.Add(new RestRuntimeOptionsConverterFactory());
options.Converters.Add(new GraphQLRuntimeOptionsConverterFactory(replaceEnvVar));
+ options.Converters.Add(new McpRuntimeOptionsConverterFactory(replaceEnvVar));
+ options.Converters.Add(new DmlToolsConfigConverter());
options.Converters.Add(new EntitySourceConverterFactory(replaceEnvVar));
options.Converters.Add(new EntityGraphQLOptionsConverterFactory(replaceEnvVar));
options.Converters.Add(new EntityRestOptionsConverterFactory(replaceEnvVar));
diff --git a/src/Core/Configurations/RuntimeConfigValidator.cs b/src/Core/Configurations/RuntimeConfigValidator.cs
index 12a8f82aa4..fd8f811c9e 100644
--- a/src/Core/Configurations/RuntimeConfigValidator.cs
+++ b/src/Core/Configurations/RuntimeConfigValidator.cs
@@ -702,11 +702,11 @@ private void ValidateNameRequirements(string entityName)
/// The config that will be validated.
public void ValidateGlobalEndpointRouteConfig(RuntimeConfig runtimeConfig)
{
- // Both REST and GraphQL endpoints cannot be disabled at the same time.
- if (!runtimeConfig.IsRestEnabled && !runtimeConfig.IsGraphQLEnabled)
+ // REST, GraphQL and MCP endpoints cannot be disabled at the same time.
+ if (!runtimeConfig.IsRestEnabled && !runtimeConfig.IsGraphQLEnabled && !runtimeConfig.IsMcpEnabled)
{
HandleOrRecordException(new DataApiBuilderException(
- message: $"Both GraphQL and REST endpoints are disabled.",
+ message: $"GraphQL, REST, and MCP endpoints are disabled.",
statusCode: HttpStatusCode.ServiceUnavailable,
subStatusCode: DataApiBuilderException.SubStatusCodes.ConfigValidationError));
}
@@ -735,19 +735,30 @@ public void ValidateGlobalEndpointRouteConfig(RuntimeConfig runtimeConfig)
ValidateRestURI(runtimeConfig);
ValidateGraphQLURI(runtimeConfig);
- // Do not check for conflicts if GraphQL or REST endpoints are disabled.
- if (!runtimeConfig.IsRestEnabled || !runtimeConfig.IsGraphQLEnabled)
+ ValidateMcpUri(runtimeConfig);
+ // Do not check for conflicts if two of the endpoints are disabled between GraphQL, REST, and MCP.
+ if ((!runtimeConfig.IsRestEnabled && !runtimeConfig.IsGraphQLEnabled) ||
+ (!runtimeConfig.IsRestEnabled && !runtimeConfig.IsMcpEnabled) ||
+ (!runtimeConfig.IsGraphQLEnabled && !runtimeConfig.IsMcpEnabled))
{
return;
}
if (string.Equals(
- a: runtimeConfig.RestPath,
- b: runtimeConfig.GraphQLPath,
- comparisonType: StringComparison.OrdinalIgnoreCase))
+ a: runtimeConfig.RestPath,
+ b: runtimeConfig.GraphQLPath,
+ comparisonType: StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(
+ a: runtimeConfig.RestPath,
+ b: runtimeConfig.McpPath,
+ comparisonType: StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(
+ a: runtimeConfig.McpPath,
+ b: runtimeConfig.GraphQLPath,
+ comparisonType: StringComparison.OrdinalIgnoreCase))
{
HandleOrRecordException(new DataApiBuilderException(
- message: $"Conflicting GraphQL and REST path configuration.",
+ message: $"Conflicting path configuration between GraphQL, REST, and MCP.",
statusCode: HttpStatusCode.ServiceUnavailable,
subStatusCode: DataApiBuilderException.SubStatusCodes.ConfigValidationError));
}
@@ -794,6 +805,41 @@ public void ValidateGraphQLURI(RuntimeConfig runtimeConfig)
}
}
+ ///
+ /// Method to validate that the MCP URI (MCP path prefix).
+ ///
+ ///
+ public void ValidateMcpUri(RuntimeConfig runtimeConfig)
+ {
+ // Skip validation if MCP is not configured
+ if (runtimeConfig.Runtime?.Mcp is null)
+ {
+ return;
+ }
+
+ // Get the MCP path from the configuration
+ string? mcpPath = runtimeConfig.Runtime.Mcp.Path;
+
+ // Validate that the path is not null or empty when MCP is configured
+ if (string.IsNullOrWhiteSpace(mcpPath))
+ {
+ HandleOrRecordException(new DataApiBuilderException(
+ message: "MCP path cannot be null or empty when MCP is configured.",
+ statusCode: HttpStatusCode.ServiceUnavailable,
+ subStatusCode: DataApiBuilderException.SubStatusCodes.ConfigValidationError));
+ return;
+ }
+
+ // Validate the MCP path using the same validation as REST and GraphQL
+ if (!RuntimeConfigValidatorUtil.TryValidateUriComponent(mcpPath, out string exceptionMsgSuffix))
+ {
+ HandleOrRecordException(new DataApiBuilderException(
+ message: $"MCP path {exceptionMsgSuffix}",
+ statusCode: HttpStatusCode.ServiceUnavailable,
+ subStatusCode: DataApiBuilderException.SubStatusCodes.ConfigValidationError));
+ }
+ }
+
private void ValidateAuthenticationOptions(RuntimeConfig runtimeConfig)
{
// Bypass validation of auth if there is no auth provided
diff --git a/src/Core/Services/RestService.cs b/src/Core/Services/RestService.cs
index 0cf9f8a374..6a2308dd83 100644
--- a/src/Core/Services/RestService.cs
+++ b/src/Core/Services/RestService.cs
@@ -391,6 +391,14 @@ public string GetRouteAfterPathBase(string route)
// forward slash '/'.
configuredRestPathBase = configuredRestPathBase.Substring(1);
+ if (route.Equals(_runtimeConfigProvider.GetConfig().McpPath.Substring(1)))
+ {
+ throw new DataApiBuilderException(
+ message: $"Route {route} was not found.",
+ statusCode: HttpStatusCode.NotFound,
+ subStatusCode: DataApiBuilderException.SubStatusCodes.GlobalMcpEndpointDisabled);
+ }
+
if (!route.StartsWith(configuredRestPathBase))
{
throw new DataApiBuilderException(
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 9213684e6e..14f097915c 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -29,6 +29,8 @@
+
+
@@ -58,25 +60,25 @@
We use an older version of Newtonsoft.Json.Schema because newer versions depend on Newtonsoft.Json >=13.0.3
which is not (and can not be made) available in Microsoft Private Nuget Feeds
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Service.Tests/Authentication/Helpers/RuntimeConfigAuthHelper.cs b/src/Service.Tests/Authentication/Helpers/RuntimeConfigAuthHelper.cs
index 12c7db4fce..07a8a565ec 100644
--- a/src/Service.Tests/Authentication/Helpers/RuntimeConfigAuthHelper.cs
+++ b/src/Service.Tests/Authentication/Helpers/RuntimeConfigAuthHelper.cs
@@ -20,6 +20,7 @@ internal static RuntimeConfig CreateTestConfigWithAuthNProvider(AuthenticationOp
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: hostOptions
),
Entities: new(new Dictionary())
diff --git a/src/Service.Tests/Authorization/AuthorizationHelpers.cs b/src/Service.Tests/Authorization/AuthorizationHelpers.cs
index 7c6948b484..85f05a1c3b 100644
--- a/src/Service.Tests/Authorization/AuthorizationHelpers.cs
+++ b/src/Service.Tests/Authorization/AuthorizationHelpers.cs
@@ -126,6 +126,7 @@ public static RuntimeConfig InitRuntimeConfig(
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(
Cors: null,
Authentication: new(authProvider, null)
diff --git a/src/Service.Tests/Authorization/AuthorizationResolverUnitTests.cs b/src/Service.Tests/Authorization/AuthorizationResolverUnitTests.cs
index 733ec15b24..39a77bffff 100644
--- a/src/Service.Tests/Authorization/AuthorizationResolverUnitTests.cs
+++ b/src/Service.Tests/Authorization/AuthorizationResolverUnitTests.cs
@@ -1441,6 +1441,7 @@ private static RuntimeConfig BuildTestRuntimeConfig(EntityPermission[] permissio
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(entityMap)
diff --git a/src/Service.Tests/Caching/HealthEndpointCachingTests.cs b/src/Service.Tests/Caching/HealthEndpointCachingTests.cs
index 2dbff7cbb2..94216a4409 100644
--- a/src/Service.Tests/Caching/HealthEndpointCachingTests.cs
+++ b/src/Service.Tests/Caching/HealthEndpointCachingTests.cs
@@ -156,6 +156,7 @@ private static void CreateCustomConfigFile(Dictionary entityMap,
Health: new(enabled: true, cacheTtlSeconds: cacheTtlSeconds),
Rest: new(Enabled: true),
GraphQL: new(Enabled: true),
+ Mcp: new(Enabled: true),
Host: hostOptions
),
Entities: new(entityMap));
diff --git a/src/Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs b/src/Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs
index 8b01d29961..963211ae40 100644
--- a/src/Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs
+++ b/src/Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs
@@ -194,6 +194,7 @@ private static RuntimeConfig CreateRuntimeConfigWithOptionalAuthN(Authentication
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: hostOptions
),
Entities: new(new Dictionary())
diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs
index 2522806049..0be24fa886 100644
--- a/src/Service.Tests/Configuration/ConfigurationTests.cs
+++ b/src/Service.Tests/Configuration/ConfigurationTests.cs
@@ -1608,7 +1608,7 @@ public async Task TestSqlMetadataForInvalidConfigEntities()
GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL),
Options: null);
- RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, new(), new());
+ RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, new(), new(), new());
// creating an entity with invalid table name
Entity entityWithInvalidSourceName = new(
@@ -1679,7 +1679,7 @@ public async Task TestSqlMetadataValidationForEntitiesWithInvalidSource()
GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL),
Options: null);
- RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, new(), new());
+ RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, new(), new(), new());
// creating an entity with invalid table name
Entity entityWithInvalidSource = new(
@@ -2214,7 +2214,7 @@ public async Task TestPathRewriteMiddlewareForGraphQL(
GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL),
Options: null);
- RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, new());
+ RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, new(), new());
const string CUSTOM_CONFIG = "custom-config.json";
File.WriteAllText(CUSTOM_CONFIG, configuration.ToJson());
@@ -2543,7 +2543,7 @@ public async Task TestGlobalFlagToEnableRestAndGraphQLForHostedAndNonHostedEnvir
DataSource dataSource = new(DatabaseType.MSSQL,
GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null);
- RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions);
+ RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, null);
const string CUSTOM_CONFIG = "custom-config.json";
File.WriteAllText(CUSTOM_CONFIG, configuration.ToJson());
@@ -2618,6 +2618,7 @@ public async Task ValidateErrorMessageForMutationWithoutReadPermission()
{
GraphQLRuntimeOptions graphqlOptions = new(Enabled: true);
RestRuntimeOptions restRuntimeOptions = new(Enabled: false);
+ McpRuntimeOptions mcpRuntimeOptions = new(Enabled: false);
DataSource dataSource = new(DatabaseType.MSSQL,
GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null);
@@ -2648,7 +2649,7 @@ public async Task ValidateErrorMessageForMutationWithoutReadPermission()
Mappings: null);
string entityName = "Stock";
- RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, entity, entityName);
+ RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, mcpRuntimeOptions, entity, entityName);
const string CUSTOM_CONFIG = "custom-config.json";
File.WriteAllText(CUSTOM_CONFIG, configuration.ToJson());
@@ -2919,6 +2920,7 @@ public async Task ValidateInheritanceOfReadPermissionFromAnonymous()
{
GraphQLRuntimeOptions graphqlOptions = new(Enabled: true);
RestRuntimeOptions restRuntimeOptions = new(Enabled: false);
+ McpRuntimeOptions mcpRuntimeOptions = new(Enabled: false);
DataSource dataSource = new(DatabaseType.MSSQL,
GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null);
@@ -2949,7 +2951,7 @@ public async Task ValidateInheritanceOfReadPermissionFromAnonymous()
Mappings: null);
string entityName = "Stock";
- RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, entity, entityName);
+ RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, mcpRuntimeOptions, entity, entityName);
const string CUSTOM_CONFIG = "custom-config.json";
File.WriteAllText(CUSTOM_CONFIG, configuration.ToJson());
@@ -3060,6 +3062,7 @@ public async Task ValidateLocationHeaderFieldForPostRequests(EntitySourceType en
GraphQLRuntimeOptions graphqlOptions = new(Enabled: false);
RestRuntimeOptions restRuntimeOptions = new(Enabled: true);
+ McpRuntimeOptions mcpRuntimeOptions = new(Enabled: false);
DataSource dataSource = new(DatabaseType.MSSQL,
GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null);
@@ -3077,11 +3080,11 @@ public async Task ValidateLocationHeaderFieldForPostRequests(EntitySourceType en
);
string entityName = "GetBooks";
- configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, entity, entityName);
+ configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, mcpRuntimeOptions, entity, entityName);
}
else
{
- configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions);
+ configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, mcpRuntimeOptions);
}
const string CUSTOM_CONFIG = "custom-config.json";
@@ -3158,6 +3161,7 @@ public async Task ValidateLocationHeaderWhenBaseRouteIsConfigured(
{
GraphQLRuntimeOptions graphqlOptions = new(Enabled: false);
RestRuntimeOptions restRuntimeOptions = new(Enabled: true);
+ McpRuntimeOptions mcpRuntimeOptions = new(Enabled: false);
DataSource dataSource = new(DatabaseType.MSSQL,
GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null);
@@ -3175,11 +3179,11 @@ public async Task ValidateLocationHeaderWhenBaseRouteIsConfigured(
);
string entityName = "GetBooks";
- configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, entity, entityName);
+ configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, mcpRuntimeOptions, entity, entityName);
}
else
{
- configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions);
+ configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, mcpRuntimeOptions);
}
const string CUSTOM_CONFIG = "custom-config.json";
@@ -3188,7 +3192,7 @@ public async Task ValidateLocationHeaderWhenBaseRouteIsConfigured(
HostOptions staticWebAppsHostOptions = new(null, authenticationOptions);
RuntimeOptions runtimeOptions = configuration.Runtime;
- RuntimeOptions baseRouteEnabledRuntimeOptions = new(runtimeOptions?.Rest, runtimeOptions?.GraphQL, staticWebAppsHostOptions, "/data-api");
+ RuntimeOptions baseRouteEnabledRuntimeOptions = new(runtimeOptions?.Rest, runtimeOptions?.GraphQL, runtimeOptions?.Mcp, staticWebAppsHostOptions, "/data-api");
RuntimeConfig baseRouteEnabledConfig = configuration with { Runtime = baseRouteEnabledRuntimeOptions };
File.WriteAllText(CUSTOM_CONFIG, baseRouteEnabledConfig.ToJson());
@@ -3347,7 +3351,7 @@ public async Task TestEngineSupportViewsWithoutKeyFieldsInConfigForMsSQL()
Mappings: null
);
- RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, new(), new(), viewEntity, "books_view_all");
+ RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, new(), new(), new(), viewEntity, "books_view_all");
const string CUSTOM_CONFIG = "custom-config.json";
@@ -3568,6 +3572,7 @@ public void TestProductionModeAppServiceEnvironmentCheck(HostMode hostMode, Easy
RuntimeOptions runtimeOptions = new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(Cors: null, authenticationOptions, hostMode)
);
RuntimeConfig configWithCustomHostMode = config with { Runtime = runtimeOptions };
@@ -3608,10 +3613,11 @@ public async Task TestSchemaIntrospectionQuery(bool enableIntrospection, bool ex
{
GraphQLRuntimeOptions graphqlOptions = new(AllowIntrospection: enableIntrospection);
RestRuntimeOptions restRuntimeOptions = new();
+ McpRuntimeOptions mcpRuntimeOptions = new();
DataSource dataSource = new(DatabaseType.MSSQL, GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null);
- RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions);
+ RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, mcpRuntimeOptions);
const string CUSTOM_CONFIG = "custom-config.json";
File.WriteAllText(CUSTOM_CONFIG, configuration.ToJson());
@@ -3660,6 +3666,7 @@ public void TestInvalidDatabaseColumnNameHandling(
{
GraphQLRuntimeOptions graphqlOptions = new(Enabled: globalGraphQLEnabled);
RestRuntimeOptions restRuntimeOptions = new(Enabled: true);
+ McpRuntimeOptions mcpOptions = new(Enabled: true);
DataSource dataSource = new(DatabaseType.MSSQL, GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null);
@@ -3683,7 +3690,7 @@ public void TestInvalidDatabaseColumnNameHandling(
Mappings: mappings
);
- RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, entity, "graphqlNameCompat");
+ RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, mcpOptions, entity, "graphqlNameCompat");
const string CUSTOM_CONFIG = "custom-config.json";
File.WriteAllText(CUSTOM_CONFIG, configuration.ToJson());
@@ -3739,7 +3746,8 @@ public async Task OpenApi_InteractiveSwaggerUI(
RuntimeConfig configuration = InitMinimalRuntimeConfig(
dataSource: dataSource,
graphqlOptions: new(),
- restOptions: new(Path: customRestPath));
+ restOptions: new(Path: customRestPath),
+ mcpOptions: new());
configuration = configuration
with
@@ -4057,6 +4065,7 @@ private static RuntimeConfig InitializeRuntimeWithLogLevel(Dictionary entityMap,
? new(
Rest: new(Enabled: enableGlobalRest),
GraphQL: new(Enabled: true),
+ Mcp: new(Enabled: true),
Host: hostOptions,
Pagination: paginationOptions)
: new(
Rest: new(Enabled: enableGlobalRest),
GraphQL: new(Enabled: true),
+ Mcp: new(Enabled: true),
Host: hostOptions);
RuntimeConfig runtimeConfig = new(
@@ -5312,6 +5324,8 @@ public static RuntimeConfig InitialzieRuntimeConfigForMultipleCreateTests(bool i
RestRuntimeOptions restRuntimeOptions = new(Enabled: false);
+ McpRuntimeOptions mcpRuntimeOptions = new(Enabled: false);
+
DataSource dataSource = new(DatabaseType.MSSQL, GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null);
EntityAction createAction = new(
@@ -5370,7 +5384,7 @@ public static RuntimeConfig InitialzieRuntimeConfigForMultipleCreateTests(bool i
RuntimeConfig runtimeConfig = new(Schema: "IntegrationTestMinimalSchema",
DataSource: dataSource,
- Runtime: new(restRuntimeOptions, graphqlOptions, Host: new(Cors: null, Authentication: authenticationOptions, Mode: HostMode.Development), Cache: null),
+ Runtime: new(restRuntimeOptions, graphqlOptions, mcpRuntimeOptions, Host: new(Cors: null, Authentication: authenticationOptions, Mode: HostMode.Development), Cache: null),
Entities: new(entityMap));
return runtimeConfig;
}
@@ -5383,6 +5397,7 @@ public static RuntimeConfig InitMinimalRuntimeConfig(
DataSource dataSource,
GraphQLRuntimeOptions graphqlOptions,
RestRuntimeOptions restOptions,
+ McpRuntimeOptions mcpOptions,
Entity entity = null,
string entityName = null,
RuntimeCacheOptions cacheOptions = null
@@ -5420,7 +5435,7 @@ public static RuntimeConfig InitMinimalRuntimeConfig(
return new(
Schema: "IntegrationTestMinimalSchema",
DataSource: dataSource,
- Runtime: new(restOptions, graphqlOptions,
+ Runtime: new(restOptions, graphqlOptions, mcpOptions,
Host: new(Cors: null, Authentication: authenticationOptions, Mode: HostMode.Development),
Cache: cacheOptions
),
@@ -5496,6 +5511,7 @@ private static RuntimeConfig CreateBasicRuntimeConfigWithNoEntity(
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(new Dictionary())
@@ -5533,6 +5549,7 @@ private static RuntimeConfig CreateBasicRuntimeConfigWithSingleEntityAndAuthOpti
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(Cors: null, Authentication: authenticationOptions)
),
Entities: new(entityMap)
diff --git a/src/Service.Tests/Configuration/HealthEndpointRolesTests.cs b/src/Service.Tests/Configuration/HealthEndpointRolesTests.cs
index a492f2c167..2a83697a3a 100644
--- a/src/Service.Tests/Configuration/HealthEndpointRolesTests.cs
+++ b/src/Service.Tests/Configuration/HealthEndpointRolesTests.cs
@@ -127,6 +127,7 @@ private static void CreateCustomConfigFile(Dictionary entityMap,
Health: new(enabled: true, roles: role != null ? new HashSet { role } : null),
Rest: new(Enabled: true),
GraphQL: new(Enabled: true),
+ Mcp: new(Enabled: true),
Host: hostOptions
),
Entities: new(entityMap));
diff --git a/src/Service.Tests/Configuration/HealthEndpointTests.cs b/src/Service.Tests/Configuration/HealthEndpointTests.cs
index 4fd2e52bf4..70e14e0108 100644
--- a/src/Service.Tests/Configuration/HealthEndpointTests.cs
+++ b/src/Service.Tests/Configuration/HealthEndpointTests.cs
@@ -53,19 +53,36 @@ public void CleanupAfterEachTest()
///
[TestMethod]
[TestCategory(TestCategory.MSSQL)]
- [DataRow(true, true, true, true, true, true, true, DisplayName = "Validate Health Report all enabled.")]
- [DataRow(false, true, true, true, true, true, true, DisplayName = "Validate when Comprehensive Health Report is disabled")]
- [DataRow(true, true, true, false, true, true, true, DisplayName = "Validate Health Report when data-source health is disabled")]
- [DataRow(true, true, true, true, false, true, true, DisplayName = "Validate Health Report when entity health is disabled")]
- [DataRow(true, false, true, true, true, true, true, DisplayName = "Validate Health Report when global rest health is disabled")]
- [DataRow(true, true, true, true, true, false, true, DisplayName = "Validate Health Report when entity rest health is disabled")]
- [DataRow(true, true, false, true, true, true, true, DisplayName = "Validate Health Report when global graphql health is disabled")]
- [DataRow(true, true, true, true, true, true, false, DisplayName = "Validate Health Report when entity graphql health is disabled")]
- public async Task ComprehensiveHealthEndpoint_ValidateContents(bool enableGlobalHealth, bool enableGlobalRest, bool enableGlobalGraphql, bool enableDatasourceHealth, bool enableEntityHealth, bool enableEntityRest, bool enableEntityGraphQL)
+ [DataRow(true, true, true, true, true, true, true, true, DisplayName = "Validate Health Report all enabled.")]
+ [DataRow(false, true, true, true, true, true, true, true, DisplayName = "Validate when Comprehensive Health Report is disabled")]
+ [DataRow(true, true, true, false, true, true, true, true, DisplayName = "Validate Health Report when global MCP health is disabled")]
+ [DataRow(true, true, true, true, false, true, true, true, DisplayName = "Validate Health Report when data-source health is disabled")]
+ [DataRow(true, true, true, true, true, false, true, true, DisplayName = "Validate Health Report when entity health is disabled")]
+ [DataRow(true, false, true, true, true, true, true, true, DisplayName = "Validate Health Report when global REST health is disabled")]
+ [DataRow(true, true, false, true, true, true, true, true, DisplayName = "Validate Health Report when global GraphQL health is disabled")]
+ [DataRow(true, true, true, true, true, true, false, true, DisplayName = "Validate Health Report when entity REST health is disabled")]
+ [DataRow(true, true, true, true, true, true, true, false, DisplayName = "Validate Health Report when entity GraphQL health is disabled")]
+ public async Task ComprehensiveHealthEndpoint_ValidateContents(
+ bool enableGlobalHealth,
+ bool enableGlobalRest,
+ bool enableGlobalGraphql,
+ bool enableGlobalMcp,
+ bool enableDatasourceHealth,
+ bool enableEntityHealth,
+ bool enableEntityRest,
+ bool enableEntityGraphQL)
{
- // Arrange
- // Create a mock entity map with a single entity for testing
- RuntimeConfig runtimeConfig = SetupCustomConfigFile(enableGlobalHealth, enableGlobalRest, enableGlobalGraphql, enableDatasourceHealth, enableEntityHealth, enableEntityRest, enableEntityGraphQL);
+ // The body remains exactly the same except passing enableGlobalMcp
+ RuntimeConfig runtimeConfig = SetupCustomConfigFile(
+ enableGlobalHealth,
+ enableGlobalRest,
+ enableGlobalGraphql,
+ enableGlobalMcp,
+ enableDatasourceHealth,
+ enableEntityHealth,
+ enableEntityRest,
+ enableEntityGraphQL);
+
WriteToCustomConfigFile(runtimeConfig);
string[] args = new[]
@@ -90,7 +107,7 @@ public async Task ComprehensiveHealthEndpoint_ValidateContents(bool enableGlobal
Assert.AreEqual(expected: HttpStatusCode.OK, actual: response.StatusCode, message: "Received unexpected HTTP code from health check endpoint.");
ValidateBasicDetailsHealthCheckResponse(responseProperties);
- ValidateConfigurationDetailsHealthCheckResponse(responseProperties, enableGlobalRest, enableGlobalGraphql);
+ ValidateConfigurationDetailsHealthCheckResponse(responseProperties, enableGlobalRest, enableGlobalGraphql, enableGlobalMcp);
ValidateIfAttributePresentInResponse(responseProperties, enableDatasourceHealth, HealthCheckConstants.DATASOURCE);
ValidateIfAttributePresentInResponse(responseProperties, enableEntityHealth, HealthCheckConstants.ENDPOINT);
if (enableEntityHealth)
@@ -110,7 +127,7 @@ public async Task ComprehensiveHealthEndpoint_ValidateContents(bool enableGlobal
public async Task TestHealthCheckRestResponseAsync()
{
// Arrange
- RuntimeConfig runtimeConfig = SetupCustomConfigFile(true, true, true, true, true, true, true);
+ RuntimeConfig runtimeConfig = SetupCustomConfigFile(true, true, true, true, true, true, true, true);
HttpUtilities httpUtilities = SetupRestTest(runtimeConfig);
// Act
@@ -139,7 +156,7 @@ public async Task TestHealthCheckRestResponseAsync()
public async Task TestFailureHealthCheckRestResponseAsync()
{
// Arrange
- RuntimeConfig runtimeConfig = SetupCustomConfigFile(true, true, true, true, true, true, true);
+ RuntimeConfig runtimeConfig = SetupCustomConfigFile(true, true, true, true, true, true, true, true);
HttpUtilities httpUtilities = SetupGraphQLTest(runtimeConfig, HttpStatusCode.BadRequest);
// Act
@@ -167,7 +184,7 @@ public async Task TestFailureHealthCheckRestResponseAsync()
public async Task TestHealthCheckGraphQLResponseAsync()
{
// Arrange
- RuntimeConfig runtimeConfig = SetupCustomConfigFile(true, true, true, true, true, true, true);
+ RuntimeConfig runtimeConfig = SetupCustomConfigFile(true, true, true, true, true, true, true, true);
HttpUtilities httpUtilities = SetupGraphQLTest(runtimeConfig);
// Act
@@ -191,7 +208,7 @@ public async Task TestHealthCheckGraphQLResponseAsync()
public async Task TestFailureHealthCheckGraphQLResponseAsync()
{
// Arrange
- RuntimeConfig runtimeConfig = SetupCustomConfigFile(true, true, true, true, true, true, true);
+ RuntimeConfig runtimeConfig = SetupCustomConfigFile(true, true, true, true, true, true, true, true);
HttpUtilities httpUtilities = SetupGraphQLTest(runtimeConfig, HttpStatusCode.InternalServerError);
// Act
@@ -427,7 +444,7 @@ private static void ValidateConfigurationIsCorrectFlag(Dictionary responseProperties, bool enableGlobalRest, bool enableGlobalGraphQL)
+ private static void ValidateConfigurationDetailsHealthCheckResponse(Dictionary responseProperties, bool enableGlobalRest, bool enableGlobalGraphQL, bool enableGlobalMcp)
{
if (responseProperties.TryGetValue("configuration", out JsonElement configElement) && configElement.ValueKind == JsonValueKind.Object)
{
@@ -443,6 +460,8 @@ private static void ValidateConfigurationDetailsHealthCheckResponse(Dictionary
@@ -520,7 +539,7 @@ private static RuntimeConfig SetupCustomConfigFile(bool enableGlobalHealth, bool
///
/// Collection of entityName -> Entity object.
/// flag to enable or disabled REST globally.
- private static RuntimeConfig CreateRuntimeConfig(Dictionary entityMap, bool enableGlobalRest = true, bool enableGlobalGraphql = true, bool enableGlobalHealth = true, bool enableDatasourceHealth = true, HostMode hostMode = HostMode.Production)
+ private static RuntimeConfig CreateRuntimeConfig(Dictionary entityMap, bool enableGlobalRest = true, bool enableGlobalGraphql = true, bool enabledGlobalMcp = true, bool enableGlobalHealth = true, bool enableDatasourceHealth = true, HostMode hostMode = HostMode.Production)
{
DataSource dataSource = new(
DatabaseType.MSSQL,
@@ -536,6 +555,7 @@ private static RuntimeConfig CreateRuntimeConfig(Dictionary enti
Health: new(enabled: enableGlobalHealth),
Rest: new(Enabled: enableGlobalRest),
GraphQL: new(Enabled: enableGlobalGraphql),
+ Mcp: new(Enabled: enabledGlobalMcp),
Host: hostOptions
),
Entities: new(entityMap));
diff --git a/src/Service.Tests/Configuration/HotReload/AuthorizationResolverHotReloadTests.cs b/src/Service.Tests/Configuration/HotReload/AuthorizationResolverHotReloadTests.cs
index cc397e0ca0..b5fcb6162b 100644
--- a/src/Service.Tests/Configuration/HotReload/AuthorizationResolverHotReloadTests.cs
+++ b/src/Service.Tests/Configuration/HotReload/AuthorizationResolverHotReloadTests.cs
@@ -131,6 +131,7 @@ private static void CreateCustomConfigFile(string fileName, Dictionary dbOptions = new();
HyphenatedNamingPolicy namingPolicy = new();
@@ -548,7 +549,7 @@ type Planet @model(name:""Planet"") {
Mappings: null);
string entityName = "Planet";
- RuntimeConfig configuration = ConfigurationTests.InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, entity, entityName);
+ RuntimeConfig configuration = ConfigurationTests.InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, mcpRuntimeOptions, entity, entityName);
const string CUSTOM_CONFIG = "custom-config.json";
const string CUSTOM_SCHEMA = "custom-schema.gql";
@@ -642,6 +643,7 @@ type Planet @model(name:""Planet"") {
}";
GraphQLRuntimeOptions graphqlOptions = new(Enabled: true);
RestRuntimeOptions restRuntimeOptions = new(Enabled: false);
+ McpRuntimeOptions mcpRuntimeOptions = new(Enabled: false);
Dictionary dbOptions = new();
HyphenatedNamingPolicy namingPolicy = new();
@@ -677,7 +679,7 @@ type Planet @model(name:""Planet"") {
Mappings: null);
string entityName = "Planet";
- RuntimeConfig configuration = ConfigurationTests.InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, entity, entityName);
+ RuntimeConfig configuration = ConfigurationTests.InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, mcpRuntimeOptions, entity, entityName);
const string CUSTOM_CONFIG = "custom-config.json";
const string CUSTOM_SCHEMA = "custom-schema.gql";
diff --git a/src/Service.Tests/CosmosTests/QueryTests.cs b/src/Service.Tests/CosmosTests/QueryTests.cs
index 97cffa3c98..c40c95c75b 100644
--- a/src/Service.Tests/CosmosTests/QueryTests.cs
+++ b/src/Service.Tests/CosmosTests/QueryTests.cs
@@ -682,6 +682,7 @@ type Planet @model(name:""Planet"") {
GraphQLRuntimeOptions graphqlOptions = new(Enabled: true);
RestRuntimeOptions restRuntimeOptions = new(Enabled: false);
+ McpRuntimeOptions mcpRuntimeOptions = new(Enabled: false);
Dictionary dbOptions = new();
HyphenatedNamingPolicy namingPolicy = new();
@@ -724,7 +725,7 @@ type Planet @model(name:""Planet"") {
string entityName = "Planet";
// cache configuration
- RuntimeConfig configuration = ConfigurationTests.InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, entity, entityName, new RuntimeCacheOptions() { Enabled = true, TtlSeconds = 5 });
+ RuntimeConfig configuration = ConfigurationTests.InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, mcpRuntimeOptions, entity, entityName, new RuntimeCacheOptions() { Enabled = true, TtlSeconds = 5 });
const string CUSTOM_CONFIG = "custom-config.json";
const string CUSTOM_SCHEMA = "custom-schema.gql";
diff --git a/src/Service.Tests/CosmosTests/SchemaGeneratorFactoryTests.cs b/src/Service.Tests/CosmosTests/SchemaGeneratorFactoryTests.cs
index 20f415e3dc..562a5174d2 100644
--- a/src/Service.Tests/CosmosTests/SchemaGeneratorFactoryTests.cs
+++ b/src/Service.Tests/CosmosTests/SchemaGeneratorFactoryTests.cs
@@ -78,7 +78,7 @@ public async Task ExportGraphQLFromCosmosDB_GeneratesSchemaSuccessfully(string g
{"database", globalDatabase},
{"container", globalContainer}
}),
- Runtime: new(Rest: null, GraphQL: new(), Host: new(null, null)),
+ Runtime: new(Rest: null, GraphQL: new(), Mcp: new(), Host: new(null, null)),
Entities: new(new Dictionary()
{
{"Container1", new Entity(
diff --git a/src/Service.Tests/GraphQLBuilder/MultipleMutationBuilderTests.cs b/src/Service.Tests/GraphQLBuilder/MultipleMutationBuilderTests.cs
index 0ed64ca6ee..94665d7c18 100644
--- a/src/Service.Tests/GraphQLBuilder/MultipleMutationBuilderTests.cs
+++ b/src/Service.Tests/GraphQLBuilder/MultipleMutationBuilderTests.cs
@@ -360,6 +360,7 @@ private static RuntimeConfigProvider GetRuntimeConfigProvider()
{
Runtime = new RuntimeOptions(Rest: runtimeConfig.Runtime.Rest,
GraphQL: new GraphQLRuntimeOptions(MultipleMutationOptions: new MultipleMutationOptions(new MultipleCreateOptions(enabled: true))),
+ Mcp: runtimeConfig.Runtime.Mcp,
Host: runtimeConfig.Runtime.Host,
BaseRoute: runtimeConfig.Runtime.BaseRoute,
Telemetry: runtimeConfig.Runtime.Telemetry,
diff --git a/src/Service.Tests/ModuleInitializer.cs b/src/Service.Tests/ModuleInitializer.cs
index b099508604..ba0407ecd5 100644
--- a/src/Service.Tests/ModuleInitializer.cs
+++ b/src/Service.Tests/ModuleInitializer.cs
@@ -51,6 +51,10 @@ public static void Init()
VerifierSettings.IgnoreMember(options => options.IsGraphQLEnabled);
// Ignore the entity IsGraphQLEnabled as that's unimportant from a test standpoint.
VerifierSettings.IgnoreMember(entity => entity.IsGraphQLEnabled);
+ // Ignore the global IsMcpEnabled as that's unimportant from a test standpoint.
+ VerifierSettings.IgnoreMember(config => config.IsMcpEnabled);
+ // Ignore the global RuntimeOptions.IsMcpEnabled as that's unimportant from a test standpoint.
+ VerifierSettings.IgnoreMember(options => options.IsMcpEnabled);
// Ignore the global IsHealthEnabled as that's unimportant from a test standpoint.
VerifierSettings.IgnoreMember(config => config.IsHealthEnabled);
// Ignore the global RuntimeOptions.IsHealthCheckEnabled as that's unimportant from a test standpoint.
@@ -69,16 +73,16 @@ public static void Init()
VerifierSettings.IgnoreMember(config => config.CosmosDataSourceUsed);
// Ignore the IsRequestBodyStrict as that's unimportant from a test standpoint.
VerifierSettings.IgnoreMember(config => config.IsRequestBodyStrict);
- // Ignore the IsGraphQLEnabled as that's unimportant from a test standpoint.
- VerifierSettings.IgnoreMember(config => config.IsGraphQLEnabled);
- // Ignore the IsRestEnabled as that's unimportant from a test standpoint.
- VerifierSettings.IgnoreMember(config => config.IsRestEnabled);
+ // Ignore the McpDmlTools as that's unimportant from a test standpoint.
+ VerifierSettings.IgnoreMember(config => config.McpDmlTools);
// Ignore the IsStaticWebAppsIdentityProvider as that's unimportant from a test standpoint.
VerifierSettings.IgnoreMember(config => config.IsStaticWebAppsIdentityProvider);
// Ignore the RestPath as that's unimportant from a test standpoint.
VerifierSettings.IgnoreMember(config => config.RestPath);
// Ignore the GraphQLPath as that's unimportant from a test standpoint.
VerifierSettings.IgnoreMember(config => config.GraphQLPath);
+ // Ignore the McpPath as that's unimportant from a test standpoint.
+ VerifierSettings.IgnoreMember(config => config.McpPath);
// Ignore the AllowIntrospection as that's unimportant from a test standpoint.
VerifierSettings.IgnoreMember(config => config.AllowIntrospection);
// Ignore the EnableAggregation as that's unimportant from a test standpoint.
@@ -105,6 +109,8 @@ public static void Init()
VerifierSettings.IgnoreMember(options => options.UserProvidedDepthLimit);
// Ignore EnableLegacyDateTimeScalar as that's not serialized in our config file.
VerifierSettings.IgnoreMember(options => options.EnableLegacyDateTimeScalar);
+ // Ignore UserProvidedPath as that's not serialized in our config file.
+ VerifierSettings.IgnoreMember(options => options.UserProvidedPath);
// Customise the path where we store snapshots, so they are easier to locate in a PR review.
VerifyBase.DerivePathInfo(
(sourceFile, projectDirectory, type, method) => new(
diff --git a/src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForCosmos.verified.txt b/src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForCosmos.verified.txt
index 51d8543ed5..420977ed26 100644
--- a/src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForCosmos.verified.txt
+++ b/src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForCosmos.verified.txt
@@ -17,6 +17,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForMsSql.verified.txt b/src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForMsSql.verified.txt
index 51b733b94e..4283eb432e 100644
--- a/src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForMsSql.verified.txt
+++ b/src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForMsSql.verified.txt
@@ -21,6 +21,10 @@
}
}
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForMySql.verified.txt b/src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForMySql.verified.txt
index 23f67259d4..f34141c964 100644
--- a/src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForMySql.verified.txt
+++ b/src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForMySql.verified.txt
@@ -13,6 +13,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForPostgreSql.verified.txt b/src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForPostgreSql.verified.txt
index a534867fee..75490a804b 100644
--- a/src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForPostgreSql.verified.txt
+++ b/src/Service.Tests/Snapshots/ConfigurationTests.TestReadingRuntimeConfigForPostgreSql.verified.txt
@@ -13,6 +13,10 @@
Path: /graphql,
AllowIntrospection: true
},
+ Mcp: {
+ Enabled: true,
+ Path: /mcp
+ },
Host: {
Cors: {
Origins: [
diff --git a/src/Service.Tests/SqlTests/GraphQLQueryTests/DwSqlGraphQLQueryTests.cs b/src/Service.Tests/SqlTests/GraphQLQueryTests/DwSqlGraphQLQueryTests.cs
index fa977e48d5..8cf55c247d 100644
--- a/src/Service.Tests/SqlTests/GraphQLQueryTests/DwSqlGraphQLQueryTests.cs
+++ b/src/Service.Tests/SqlTests/GraphQLQueryTests/DwSqlGraphQLQueryTests.cs
@@ -1239,6 +1239,7 @@ public void TestEnableDwNto1JoinQueryFeatureFlagLoadedFromRuntime()
{
EnableDwNto1JoinQueryOptimization = true
}),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(new Dictionary())
@@ -1261,6 +1262,7 @@ public void TestEnableDwNto1JoinQueryFeatureFlagDefaultValueLoaded()
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(new Dictionary())
diff --git a/src/Service.Tests/SqlTests/SqlTestHelper.cs b/src/Service.Tests/SqlTests/SqlTestHelper.cs
index 6193d843a0..e739f6cc8c 100644
--- a/src/Service.Tests/SqlTests/SqlTestHelper.cs
+++ b/src/Service.Tests/SqlTests/SqlTestHelper.cs
@@ -389,6 +389,7 @@ public static RuntimeConfig InitBasicRuntimeConfigWithNoEntity(
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(Cors: null, Authentication: authenticationOptions)
),
Entities: new(new Dictionary())
diff --git a/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs b/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs
index 5a9d783376..16caf29b49 100644
--- a/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs
+++ b/src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs
@@ -257,6 +257,7 @@ public void TestAddingRelationshipWithInvalidTargetEntity()
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(entityMap)
@@ -317,6 +318,7 @@ public void TestAddingRelationshipWithDisabledGraphQL()
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(entityMap)
@@ -373,6 +375,7 @@ string relationshipEntity
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(entityMap)
@@ -461,6 +464,7 @@ public void TestRelationshipWithNoLinkingObjectAndEitherSourceOrTargetFieldIsNul
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(entityMap));
@@ -553,6 +557,7 @@ public void TestRelationshipWithoutSourceAndTargetFieldsMatching(
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(entityMap));
@@ -626,6 +631,7 @@ public void TestRelationshipWithoutSourceAndTargetFieldsAsValidBackingColumns(
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(entityMap));
@@ -755,6 +761,7 @@ public void TestRelationshipWithoutLinkingSourceAndTargetFieldsMatching(
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(entityMap));
@@ -1011,6 +1018,7 @@ public void TestOperationValidityAndCasing(string operationName, bool exceptionE
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(entityMap));
@@ -1083,6 +1091,7 @@ public void ValidateGraphQLTypeNamesFromConfig(string entityNameFromConfig, bool
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(entityMap)
@@ -1440,21 +1449,27 @@ public void ValidateValidEntityDefinitionsDoesNotGenerateDuplicateQueries(Databa
///
/// GraphQL global path
/// REST global path
+ /// MCP global path
/// Exception expected
[DataTestMethod]
- [DataRow("/graphql", "/graphql", true)]
- [DataRow("/api", "/api", true)]
- [DataRow("/graphql", "/api", false)]
- public void TestGlobalRouteValidation(string graphQLConfiguredPath, string restConfiguredPath, bool expectError)
+ [DataRow("/graphql", "/graphql", "/mcp", true, DisplayName = "GraphQL and REST conflict (same path).")]
+ [DataRow("/api", "/api", "/mcp", true, DisplayName = "REST and GraphQL conflict (same path).")]
+ [DataRow("/graphql", "/api", "/mcp", false, DisplayName = "GraphQL, REST, and MCP distinct.")]
+ // Extra case: conflict with MCP
+ [DataRow("/mcp", "/api", "/mcp", true, DisplayName = "MCP and GraphQL conflict (same path).")]
+ [DataRow("/graphql", "/mcp", "/mcp", true, DisplayName = "MCP and REST conflict (same path).")]
+ public void TestGlobalRouteValidation(string graphQLConfiguredPath, string restConfiguredPath, string mcpConfiguredPath, bool expectError)
{
GraphQLRuntimeOptions graphQL = new(Path: graphQLConfiguredPath);
RestRuntimeOptions rest = new(Path: restConfiguredPath);
+ McpRuntimeOptions mcp = new(Path: mcpConfiguredPath);
RuntimeConfig configuration = ConfigurationTests.InitMinimalRuntimeConfig(
new(DatabaseType.MSSQL, "", Options: null),
graphQL,
- rest);
- string expectedErrorMessage = "Conflicting GraphQL and REST path configuration.";
+ rest,
+ mcp);
+ string expectedErrorMessage = "Conflicting path configuration between GraphQL, REST, and MCP.";
try
{
@@ -1671,11 +1686,16 @@ public void ValidateApiURIsAreWellFormed(
{
string graphQLPathPrefix = GraphQLRuntimeOptions.DEFAULT_PATH;
string restPathPrefix = RestRuntimeOptions.DEFAULT_PATH;
+ string mcpPathPrefix = McpRuntimeOptions.DEFAULT_PATH;
if (apiType is ApiType.REST)
{
restPathPrefix = apiPathPrefix;
}
+ else if (apiType is ApiType.MCP)
+ {
+ mcpPathPrefix = apiPathPrefix;
+ }
else
{
graphQLPathPrefix = apiPathPrefix;
@@ -1683,11 +1703,13 @@ public void ValidateApiURIsAreWellFormed(
GraphQLRuntimeOptions graphQL = new(Path: graphQLPathPrefix);
RestRuntimeOptions rest = new(Path: restPathPrefix);
+ McpRuntimeOptions mcp = new(Enabled: false);
RuntimeConfig configuration = ConfigurationTests.InitMinimalRuntimeConfig(
new(DatabaseType.MSSQL, "", Options: null),
graphQL,
- rest);
+ rest,
+ mcp);
RuntimeConfigValidator configValidator = InitializeRuntimeConfigValidator();
@@ -1710,25 +1732,33 @@ public void ValidateApiURIsAreWellFormed(
///
/// Boolean flag to indicate if REST endpoints are enabled globally.
/// Boolean flag to indicate if GraphQL endpoints are enabled globally.
+ /// Boolean flag to indicate if MCP endpoints are enabled globally.
/// Boolean flag to indicate if exception is expected.
- [DataRow(true, true, false, DisplayName = "Both REST and GraphQL enabled.")]
- [DataRow(true, false, false, DisplayName = "REST enabled, and GraphQL disabled.")]
- [DataRow(false, true, false, DisplayName = "REST disabled, and GraphQL enabled.")]
- [DataRow(false, false, true, DisplayName = "Both REST and GraphQL are disabled.")]
+ [DataRow(true, true, true, false, DisplayName = "REST, GraphQL, and MCP enabled.")]
+ [DataRow(true, true, false, false, DisplayName = "REST and GraphQL enabled, MCP disabled.")]
+ [DataRow(true, false, true, false, DisplayName = "REST enabled, GraphQL disabled, and MCP enabled.")]
+ [DataRow(true, false, false, false, DisplayName = "REST enabled, GraphQL and MCP disabled.")]
+ [DataRow(false, true, true, false, DisplayName = "REST disabled, GraphQL and MCP enabled.")]
+ [DataRow(false, true, false, false, DisplayName = "REST disabled, GraphQL enabled, and MCP disabled.")]
+ [DataRow(false, false, true, false, DisplayName = "REST and GraphQL disabled, MCP enabled.")]
+ [DataRow(false, false, false, true, DisplayName = "REST, GraphQL, and MCP disabled.")]
[DataTestMethod]
- public void EnsureFailureWhenBothRestAndGraphQLAreDisabled(
+ public void EnsureFailureWhenRestAndGraphQLAndMcpAreDisabled(
bool restEnabled,
bool graphqlEnabled,
+ bool mcpEnabled,
bool expectError)
{
GraphQLRuntimeOptions graphQL = new(Enabled: graphqlEnabled);
RestRuntimeOptions rest = new(Enabled: restEnabled);
+ McpRuntimeOptions mcp = new(Enabled: mcpEnabled);
RuntimeConfig configuration = ConfigurationTests.InitMinimalRuntimeConfig(
new(DatabaseType.MSSQL, "", Options: null),
graphQL,
- rest);
- string expectedErrorMessage = "Both GraphQL and REST endpoints are disabled.";
+ rest,
+ mcp);
+ string expectedErrorMessage = "GraphQL, REST, and MCP endpoints are disabled.";
try
{
@@ -1995,6 +2025,7 @@ public void ValidateRestMethodsForEntityInConfig(
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)),
Entities: new(entityMap));
@@ -2068,6 +2099,7 @@ public void ValidateRestPathForEntityInConfig(
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(entityMap)
@@ -2138,6 +2170,7 @@ public void ValidateUniqueRestPathsForEntitiesInConfig(
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(entityMap)
@@ -2198,6 +2231,7 @@ public void ValidateRuntimeBaseRouteSettings(
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(Cors: null, Authentication: new(Provider: authenticationProvider, Jwt: null)),
BaseRoute: runtimeBaseRoute
),
@@ -2334,6 +2368,7 @@ public void TestRuntimeConfigSetupWithNonJsonConstructor()
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new RuntimeEntities(entityMap),
@@ -2405,6 +2440,7 @@ public void ValidatePaginationOptionsInConfig(
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(Cors: null, Authentication: null),
Pagination: new PaginationOptions(defaultPageSize, maxPageSize, nextLinkRelative)
),
@@ -2456,6 +2492,7 @@ public void ValidateMaxResponseSizeInConfig(
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(Cors: null, Authentication: null, MaxResponseSizeMB: providedMaxResponseSizeMB)
),
Entities: new(new Dictionary()));
diff --git a/src/Service.Tests/UnitTests/DbExceptionParserUnitTests.cs b/src/Service.Tests/UnitTests/DbExceptionParserUnitTests.cs
index ba7f05251a..02801de3e2 100644
--- a/src/Service.Tests/UnitTests/DbExceptionParserUnitTests.cs
+++ b/src/Service.Tests/UnitTests/DbExceptionParserUnitTests.cs
@@ -38,6 +38,7 @@ public void VerifyCorrectErrorMessage(bool isDeveloperMode, string expected)
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null, isDeveloperMode ? HostMode.Development : HostMode.Production)
),
Entities: new(new Dictionary())
@@ -80,6 +81,7 @@ public void TestIsTransientExceptionMethod(bool expected, int number)
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null, HostMode.Development)
),
Entities: new(new Dictionary())
diff --git a/src/Service.Tests/UnitTests/MultiSourceQueryExecutionUnitTests.cs b/src/Service.Tests/UnitTests/MultiSourceQueryExecutionUnitTests.cs
index dd76845a04..e06e140328 100644
--- a/src/Service.Tests/UnitTests/MultiSourceQueryExecutionUnitTests.cs
+++ b/src/Service.Tests/UnitTests/MultiSourceQueryExecutionUnitTests.cs
@@ -251,6 +251,7 @@ public async Task TestMultiSourceTokenSet()
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(Cors: null, Authentication: null)
),
DefaultDataSourceName: DATA_SOURCE_NAME_1,
@@ -312,6 +313,7 @@ private static RuntimeConfig GenerateMockRuntimeConfigForMultiDbScenario()
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
// use prod mode to avoid having to mock config file watcher
Host: new(Cors: null, Authentication: null, HostMode.Production)
),
diff --git a/src/Service.Tests/UnitTests/MySqlQueryExecutorUnitTests.cs b/src/Service.Tests/UnitTests/MySqlQueryExecutorUnitTests.cs
index 423234aa73..cbfef36664 100644
--- a/src/Service.Tests/UnitTests/MySqlQueryExecutorUnitTests.cs
+++ b/src/Service.Tests/UnitTests/MySqlQueryExecutorUnitTests.cs
@@ -46,6 +46,7 @@ public async Task TestHandleManagedIdentityAccess(
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(new Dictionary())
diff --git a/src/Service.Tests/UnitTests/PostgreSqlQueryExecutorUnitTests.cs b/src/Service.Tests/UnitTests/PostgreSqlQueryExecutorUnitTests.cs
index f0db8b4742..ccaa90b353 100644
--- a/src/Service.Tests/UnitTests/PostgreSqlQueryExecutorUnitTests.cs
+++ b/src/Service.Tests/UnitTests/PostgreSqlQueryExecutorUnitTests.cs
@@ -57,6 +57,7 @@ public async Task TestHandleManagedIdentityAccess(
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(new Dictionary())
diff --git a/src/Service.Tests/UnitTests/RequestValidatorUnitTests.cs b/src/Service.Tests/UnitTests/RequestValidatorUnitTests.cs
index a19823df18..186f254c51 100644
--- a/src/Service.Tests/UnitTests/RequestValidatorUnitTests.cs
+++ b/src/Service.Tests/UnitTests/RequestValidatorUnitTests.cs
@@ -356,6 +356,7 @@ public static void PerformTest(
Runtime: new(
Rest: new(Path: "/api"),
GraphQL: new(),
+ Mcp: new(),
Host: new(Cors: null, Authentication: null)
),
Entities: new(new Dictionary()
diff --git a/src/Service.Tests/UnitTests/RestServiceUnitTests.cs b/src/Service.Tests/UnitTests/RestServiceUnitTests.cs
index 9d483bf1d2..1fa1a276ad 100644
--- a/src/Service.Tests/UnitTests/RestServiceUnitTests.cs
+++ b/src/Service.Tests/UnitTests/RestServiceUnitTests.cs
@@ -115,6 +115,7 @@ public static void InitializeTest(string restRoutePrefix, string entityName)
Runtime: new(
Rest: new(Path: restRoutePrefix),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(new Dictionary())
diff --git a/src/Service.Tests/UnitTests/RuntimeConfigLoaderJsonDeserializerTests.cs b/src/Service.Tests/UnitTests/RuntimeConfigLoaderJsonDeserializerTests.cs
index 8d7dae0541..b98de993e2 100644
--- a/src/Service.Tests/UnitTests/RuntimeConfigLoaderJsonDeserializerTests.cs
+++ b/src/Service.Tests/UnitTests/RuntimeConfigLoaderJsonDeserializerTests.cs
@@ -259,7 +259,7 @@ public void TestNullableOptionalProps()
TryParseAndAssertOnDefaults("{" + emptyRuntime, out _);
// Test with empty sub properties of runtime
- minJson.Append(@"{ ""rest"": { }, ""graphql"": { },
+ minJson.Append(@"{ ""rest"": { }, ""graphql"": { }, ""mcp"": { },
""base-route"" : """",");
StringBuilder minJsonWithHostSubProps = new(minJson + @"""telemetry"" : { }, ""host"" : ");
StringBuilder minJsonWithTelemetrySubProps = new(minJson + @"""host"" : { }, ""telemetry"" : ");
@@ -423,6 +423,10 @@ public static string GetModifiedJsonString(string[] reps, string enumString)
}
}
},
+ ""mcp"": {
+ ""enabled"": true,
+ ""path"": """ + reps[++index % reps.Length] + @"""
+ },
""host"": {
""mode"": ""development"",
""cors"": {
@@ -506,6 +510,10 @@ public static string GetModifiedJsonString(string[] reps, string enumString)
""enabled"": true,
""path"": ""/graphql""
},
+ ""mcp"": {
+ ""enabled"": true,
+ ""path"": ""/mcp""
+ },
""host"": {
""mode"": ""development"",
""cors"": {
@@ -641,6 +649,8 @@ private static bool TryParseAndAssertOnDefaults(string json, out RuntimeConfig p
Assert.AreEqual(RestRuntimeOptions.DEFAULT_PATH, parsedConfig.RestPath);
Assert.IsTrue(parsedConfig.IsGraphQLEnabled);
Assert.AreEqual(GraphQLRuntimeOptions.DEFAULT_PATH, parsedConfig.GraphQLPath);
+ Assert.IsTrue(parsedConfig.IsMcpEnabled);
+ Assert.AreEqual(McpRuntimeOptions.DEFAULT_PATH, parsedConfig.McpPath);
Assert.IsTrue(parsedConfig.AllowIntrospection);
Assert.IsFalse(parsedConfig.IsDevelopmentMode());
Assert.IsTrue(parsedConfig.IsStaticWebAppsIdentityProvider);
diff --git a/src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs b/src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs
index 92b076107a..908b7019c4 100644
--- a/src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs
+++ b/src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs
@@ -80,6 +80,7 @@ public async Task TestHandleManagedIdentityAccess(
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(new Dictionary())
@@ -154,6 +155,7 @@ public async Task TestRetryPolicyExhaustingMaxAttempts()
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(new Dictionary())
@@ -229,6 +231,7 @@ public void Test_DbCommandParameter_PopulatedWithCorrectDbTypes()
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(new Dictionary())
@@ -344,6 +347,7 @@ public async Task TestHttpContextIsPopulatedWithDbExecutionTime()
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(new Dictionary())
@@ -446,6 +450,7 @@ public void TestToValidateLockingOfHttpContextObjectDuringCalcuationOfDbExecutio
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(null, null)
),
Entities: new(new Dictionary())
@@ -512,6 +517,7 @@ public void ValidateStreamingLogicAsync(int readDataLoops, bool exceptionExpecte
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(Cors: null, Authentication: null, MaxResponseSizeMB: 5)
),
Entities: new(new Dictionary()));
@@ -573,6 +579,7 @@ public void ValidateStreamingLogicForStoredProcedures(int readDataLoops, bool ex
Runtime: new(
Rest: new(),
GraphQL: new(),
+ Mcp: new(),
Host: new(Cors: null, Authentication: null, MaxResponseSizeMB: 4)
),
Entities: new(new Dictionary()));
diff --git a/src/Service.Tests/dab-config.MsSql.json b/src/Service.Tests/dab-config.MsSql.json
index e57c7dce8c..d5e903d4f3 100644
--- a/src/Service.Tests/dab-config.MsSql.json
+++ b/src/Service.Tests/dab-config.MsSql.json
@@ -23,6 +23,11 @@
}
}
},
+ "mcp": {
+ "enabled": true,
+ "path": "/mcp",
+ "dml-tools": true
+ },
"host": {
"cors": {
"origins": [
@@ -2314,7 +2319,8 @@
"Notebook": {
"source": {
"object": "notebooks",
- "type": "table"
+ "type": "table",
+ "object-description": "Table containing notebook information"
},
"graphql": {
"enabled": true,
@@ -3843,4 +3849,4 @@
]
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Service/Azure.DataApiBuilder.Service.csproj b/src/Service/Azure.DataApiBuilder.Service.csproj
index 9f1558e504..6ea9c8dad2 100644
--- a/src/Service/Azure.DataApiBuilder.Service.csproj
+++ b/src/Service/Azure.DataApiBuilder.Service.csproj
@@ -1,4 +1,4 @@
-
+
net8.0
@@ -102,6 +102,7 @@
+
diff --git a/src/Service/HealthCheck/HealthCheckHelper.cs b/src/Service/HealthCheck/HealthCheckHelper.cs
index 9225c3aeb0..452cb803a9 100644
--- a/src/Service/HealthCheck/HealthCheckHelper.cs
+++ b/src/Service/HealthCheck/HealthCheckHelper.cs
@@ -140,6 +140,7 @@ private static void UpdateDabConfigurationDetails(ref ComprehensiveHealthCheckRe
{
Rest = runtimeConfig.IsRestEnabled,
GraphQL = runtimeConfig.IsGraphQLEnabled,
+ Mcp = runtimeConfig.IsMcpEnabled,
Caching = runtimeConfig.IsCachingEnabled,
Telemetry = runtimeConfig?.Runtime?.Telemetry != null,
Mode = runtimeConfig?.Runtime?.Host?.Mode ?? HostMode.Production, // Modify to runtimeConfig.HostMode in Roles PR
diff --git a/src/Service/HealthCheck/Model/ConfigurationDetails.cs b/src/Service/HealthCheck/Model/ConfigurationDetails.cs
index c3989e0167..9ff007754e 100644
--- a/src/Service/HealthCheck/Model/ConfigurationDetails.cs
+++ b/src/Service/HealthCheck/Model/ConfigurationDetails.cs
@@ -18,6 +18,9 @@ public record ConfigurationDetails
[JsonPropertyName("graphql")]
public bool GraphQL { get; init; }
+ [JsonPropertyName("mcp")]
+ public bool Mcp { get; init; }
+
[JsonPropertyName("caching")]
public bool Caching { get; init; }
diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs
index a23c23178a..48a39d31d0 100644
--- a/src/Service/Startup.cs
+++ b/src/Service/Startup.cs
@@ -24,6 +24,7 @@
using Azure.DataApiBuilder.Core.Services.MetadataProviders;
using Azure.DataApiBuilder.Core.Services.OpenAPI;
using Azure.DataApiBuilder.Core.Telemetry;
+using Azure.DataApiBuilder.Mcp.Core;
using Azure.DataApiBuilder.Service.Controllers;
using Azure.DataApiBuilder.Service.Exceptions;
using Azure.DataApiBuilder.Service.HealthCheck;
@@ -452,6 +453,9 @@ public void ConfigureServices(IServiceCollection services)
}
services.AddSingleton();
+
+ services.AddDabMcpServer(configProvider);
+
services.AddControllers();
}
@@ -678,6 +682,9 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, RuntimeC
{
endpoints.MapControllers();
+ // Special for MCP
+ endpoints.MapDabMcp(runtimeConfigProvider);
+
endpoints
.MapGraphQL()
.WithOptions(new GraphQLServerOptions