-
Notifications
You must be signed in to change notification settings - Fork 297
Cherry pick [MCP] Added support for --mcp-stdio flag to dab start (#2983) to release 1.7
#3034
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release/1.7
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| using Microsoft.Extensions.Configuration; | ||
|
|
||
| namespace Azure.DataApiBuilder.Mcp.Core | ||
| { | ||
| /// <summary> | ||
| /// Centralized defaults and configuration keys for MCP protocol settings. | ||
| /// </summary> | ||
| public static class McpProtocolDefaults | ||
| { | ||
| /// <summary> | ||
| /// Default MCP protocol version advertised when no configuration override is provided. | ||
| /// </summary> | ||
| public const string DEFAULT_PROTOCOL_VERSION = "2025-06-18"; | ||
|
|
||
| /// <summary> | ||
| /// Configuration key used to override the MCP protocol version. | ||
| /// </summary> | ||
| public const string PROTOCOL_VERSION_CONFIG_KEY = "MCP:ProtocolVersion"; | ||
|
|
||
| /// <summary> | ||
| /// Helper to resolve the effective protocol version from configuration. | ||
| /// Falls back to <see cref="DEFAULT_PROTOCOL_VERSION"/> when the key is not set. | ||
| /// </summary> | ||
| public static string ResolveProtocolVersion(IConfiguration? configuration) | ||
| { | ||
| return configuration?.GetValue<string>(PROTOCOL_VERSION_CONFIG_KEY) ?? DEFAULT_PROTOCOL_VERSION; | ||
| } | ||
| } | ||
| } | ||
|
|
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| namespace Azure.DataApiBuilder.Mcp.Core | ||
| { | ||
| public interface IMcpStdioServer | ||
| { | ||
| Task RunAsync(CancellationToken cancellationToken); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -19,12 +19,14 @@ public class StartOptions : Options | |||||
| { | ||||||
| private const string LOGLEVEL_HELPTEXT = "Specifies logging level as provided value. For possible values, see: https://go.microsoft.com/fwlink/?linkid=2263106"; | ||||||
|
|
||||||
| public StartOptions(bool verbose, LogLevel? logLevel, bool isHttpsRedirectionDisabled, string config) | ||||||
| public StartOptions(bool verbose, LogLevel? logLevel, bool isHttpsRedirectionDisabled, bool mcpStdio, string? mcpRole, string config) | ||||||
| : base(config) | ||||||
| { | ||||||
| // When verbose is true we set LogLevel to information. | ||||||
| LogLevel = verbose is true ? Microsoft.Extensions.Logging.LogLevel.Information : logLevel; | ||||||
| IsHttpsRedirectionDisabled = isHttpsRedirectionDisabled; | ||||||
| McpStdio = mcpStdio; | ||||||
| McpRole = mcpRole; | ||||||
| } | ||||||
|
|
||||||
| // SetName defines mutually exclusive sets, ie: can not have | ||||||
|
|
@@ -38,14 +40,21 @@ public StartOptions(bool verbose, LogLevel? logLevel, bool isHttpsRedirectionDis | |||||
| [Option("no-https-redirect", Required = false, HelpText = "Disables automatic https redirects.")] | ||||||
| public bool IsHttpsRedirectionDisabled { get; } | ||||||
|
|
||||||
| [Option("mcp-stdio", Required = false, HelpText = "Run Data API Builder in MCP stdio mode while starting the engine.")] | ||||||
| public bool McpStdio { get; } | ||||||
|
|
||||||
| [Value(0, MetaName = "role", Required = false, HelpText = "Optional MCP permissions role, e.g. role:anonymous. If omitted, defaults to anonymous.")] | ||||||
|
||||||
| [Value(0, MetaName = "role", Required = false, HelpText = "Optional MCP permissions role, e.g. role:anonymous. If omitted, defaults to anonymous.")] | |
| [Value(0, MetaName = "role", Required = false, HelpText = "Optional MCP permissions role name, e.g. anonymous. If omitted, defaults to anonymous.")] |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2359,6 +2359,17 @@ public static bool TryStartEngineWithOptions(StartOptions options, FileSystemRun | |||||
| args.Add(Startup.NO_HTTPS_REDIRECT_FLAG); | ||||||
| } | ||||||
|
|
||||||
| // If MCP stdio was requested, append the stdio-specific switches. | ||||||
| if (options.McpStdio) | ||||||
| { | ||||||
| string effectiveRole = string.IsNullOrWhiteSpace(options.McpRole) | ||||||
| ? "anonymous" | ||||||
| : options.McpRole; | ||||||
|
|
||||||
| args.Add("--mcp-stdio"); | ||||||
| args.Add(effectiveRole); | ||||||
|
||||||
| args.Add(effectiveRole); | |
| args.Add($"role:{effectiveRole}"); |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -110,7 +110,13 @@ private static async Task ExportGraphQL( | |||||||||||||
| } | ||||||||||||||
| else | ||||||||||||||
| { | ||||||||||||||
| StartOptions startOptions = new(false, LogLevel.None, false, options.Config!); | ||||||||||||||
| StartOptions startOptions = new( | ||||||||||||||
| verbose: false, | ||||||||||||||
| logLevel: LogLevel.None, | ||||||||||||||
| isHttpsRedirectionDisabled: false, | ||||||||||||||
| config: options.Config!, | ||||||||||||||
| mcpStdio: false, | ||||||||||||||
| mcpRole: null); | ||||||||||||||
|
Comment on lines
+117
to
+119
|
||||||||||||||
| config: options.Config!, | |
| mcpStdio: false, | |
| mcpRole: null); | |
| mcpStdio: false, | |
| mcpRole: null, | |
| config: options.Config!); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,20 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System; | ||
|
||
| using System.CommandLine; | ||
| using System.CommandLine.Parsing; | ||
| using System.Runtime.InteropServices; | ||
| using System.Text; | ||
| using System.Text.RegularExpressions; | ||
| using System.Threading.Tasks; | ||
| using Azure.DataApiBuilder.Config; | ||
| using Azure.DataApiBuilder.Service.Exceptions; | ||
| using Azure.DataApiBuilder.Service.Telemetry; | ||
| using Azure.DataApiBuilder.Service.Utilities; | ||
| using Microsoft.ApplicationInsights; | ||
| using Microsoft.AspNetCore; | ||
| using Microsoft.AspNetCore.Builder; | ||
| using Microsoft.AspNetCore.Hosting; | ||
| using Microsoft.Extensions.Configuration; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Hosting; | ||
| using Microsoft.Extensions.Logging; | ||
| using Microsoft.Extensions.Logging.ApplicationInsights; | ||
|
|
@@ -33,27 +33,41 @@ public class Program | |
|
|
||
| public static void Main(string[] args) | ||
| { | ||
| bool runMcpStdio = McpStdioHelper.ShouldRunMcpStdio(args, out string? mcpRole); | ||
|
|
||
| if (runMcpStdio) | ||
| { | ||
| Console.OutputEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); | ||
| Console.InputEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); | ||
| } | ||
|
|
||
| if (!ValidateAspNetCoreUrls()) | ||
| { | ||
| Console.Error.WriteLine("Invalid ASPNETCORE_URLS format. e.g.: ASPNETCORE_URLS=\"http://localhost:5000;https://localhost:5001\""); | ||
| Environment.ExitCode = -1; | ||
| return; | ||
| } | ||
|
|
||
| if (!StartEngine(args)) | ||
| if (!StartEngine(args, runMcpStdio, mcpRole)) | ||
| { | ||
| Environment.ExitCode = -1; | ||
| } | ||
| } | ||
|
|
||
| public static bool StartEngine(string[] args) | ||
| public static bool StartEngine(string[] args, bool runMcpStdio, string? mcpRole) | ||
| { | ||
| // Unable to use ILogger because this code is invoked before LoggerFactory | ||
| // is instantiated. | ||
| Console.WriteLine("Starting the runtime engine..."); | ||
| try | ||
| { | ||
| CreateHostBuilder(args).Build().Run(); | ||
| IHost host = CreateHostBuilder(args, runMcpStdio, mcpRole).Build(); | ||
|
|
||
| if (runMcpStdio) | ||
| { | ||
| return McpStdioHelper.RunMcpStdioHost(host); | ||
| } | ||
|
|
||
| // Normal web mode | ||
| host.Run(); | ||
| return true; | ||
| } | ||
| // Catch exception raised by explicit call to IHostApplicationLifetime.StopApplication() | ||
|
|
@@ -72,17 +86,28 @@ public static bool StartEngine(string[] args) | |
| } | ||
| } | ||
|
|
||
| public static IHostBuilder CreateHostBuilder(string[] args) | ||
| // Compatibility overload used by external callers that do not pass the runMcpStdio flag. | ||
| public static bool StartEngine(string[] args) | ||
| { | ||
| bool runMcpStdio = McpStdioHelper.ShouldRunMcpStdio(args, out string? mcpRole); | ||
| return StartEngine(args, runMcpStdio, mcpRole: mcpRole); | ||
| } | ||
|
|
||
| public static IHostBuilder CreateHostBuilder(string[] args, bool runMcpStdio, string? mcpRole) | ||
| { | ||
| return Host.CreateDefaultBuilder(args) | ||
| .ConfigureAppConfiguration(builder => | ||
| { | ||
| AddConfigurationProviders(builder, args); | ||
| if (runMcpStdio) | ||
| { | ||
| McpStdioHelper.ConfigureMcpStdio(builder, mcpRole); | ||
| } | ||
| }) | ||
| .ConfigureWebHostDefaults(webBuilder => | ||
| { | ||
| Startup.MinimumLogLevel = GetLogLevelFromCommandLineArgs(args, out Startup.IsLogLevelOverriddenByCli); | ||
| ILoggerFactory loggerFactory = GetLoggerFactoryForLogLevel(Startup.MinimumLogLevel); | ||
| ILoggerFactory loggerFactory = GetLoggerFactoryForLogLevel(Startup.MinimumLogLevel, stdio: runMcpStdio); | ||
| ILogger<Startup> startupLogger = loggerFactory.CreateLogger<Startup>(); | ||
| DisableHttpsRedirectionIfNeeded(args); | ||
| webBuilder.UseStartup(builder => new Startup(builder.Configuration, startupLogger)); | ||
|
|
@@ -140,7 +165,14 @@ private static ParseResult GetParseResult(Command cmd, string[] args) | |
| /// <param name="appTelemetryClient">Telemetry client</param> | ||
| /// <param name="logLevelInitializer">Hot-reloadable log level</param> | ||
| /// <param name="serilogLogger">Core Serilog logging pipeline</param> | ||
| public static ILoggerFactory GetLoggerFactoryForLogLevel(LogLevel logLevel, TelemetryClient? appTelemetryClient = null, LogLevelInitializer? logLevelInitializer = null, Logger? serilogLogger = null) | ||
| /// <param name="stdio">Whether the logger is for stdio mode</param> | ||
| /// <returns>ILoggerFactory</returns> | ||
| public static ILoggerFactory GetLoggerFactoryForLogLevel( | ||
| LogLevel logLevel, | ||
| TelemetryClient? appTelemetryClient = null, | ||
| LogLevelInitializer? logLevelInitializer = null, | ||
| Logger? serilogLogger = null, | ||
| bool stdio = false) | ||
| { | ||
| return LoggerFactory | ||
| .Create(builder => | ||
|
|
@@ -229,7 +261,19 @@ public static ILoggerFactory GetLoggerFactoryForLogLevel(LogLevel logLevel, Tele | |
| } | ||
| } | ||
|
|
||
| builder.AddConsole(); | ||
| // In stdio mode, route console logs to STDERR to keep STDOUT clean for MCP JSON | ||
| if (stdio) | ||
| { | ||
| builder.ClearProviders(); | ||
| builder.AddConsole(options => | ||
| { | ||
| options.LogToStandardErrorThreshold = LogLevel.Trace; | ||
| }); | ||
|
Comment on lines
+267
to
+271
|
||
| } | ||
| else | ||
| { | ||
| builder.AddConsole(); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,97 @@ | ||||||||||
| using System; | ||||||||||
| using System.Collections.Generic; | ||||||||||
| using Microsoft.Extensions.Configuration; | ||||||||||
| using Microsoft.Extensions.DependencyInjection; | ||||||||||
| using Microsoft.Extensions.Hosting; | ||||||||||
|
|
||||||||||
| namespace Azure.DataApiBuilder.Service.Utilities | ||||||||||
| { | ||||||||||
| /// <summary> | ||||||||||
| /// Helper methods for configuring and running MCP in stdio mode. | ||||||||||
| /// </summary> | ||||||||||
| internal static class McpStdioHelper | ||||||||||
| { | ||||||||||
| /// <summary> | ||||||||||
| /// Determines if MCP stdio mode should be run based on command line arguments. | ||||||||||
| /// </summary> | ||||||||||
| /// <param name="args"> The command line arguments.</param> | ||||||||||
| /// <param name="mcpRole"> The role for MCP stdio mode, if specified.</param> | ||||||||||
| /// <returns></returns> | ||||||||||
|
||||||||||
| /// <returns></returns> | |
| /// <returns><see langword="true"/> if MCP stdio mode should be run; otherwise, <see langword="false"/>.</returns> |
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parameter name in the XML documentation does not match the actual parameter name. The documentation says "builder" but the parameter is named "host".
Copilot
AI
Dec 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method call to tool.GetToolMetadata() is made but its return value is discarded using the discard operator. If this is intentionally calling the method for side effects only (such as ensuring lazy initialization), this should be documented with a comment explaining why. Otherwise, if the metadata is not needed, consider whether this call is necessary.
| { | |
| { | |
| // Ensure the tool's metadata is initialized/validated before registration. | |
| // The return value is intentionally discarded; only side effects are required here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The interface is missing necessary using directives. The
Tasktype is used in the method signature but theSystem.Threading.Tasksnamespace is not imported. Additionally,CancellationTokenrequiresSystem.Threadingnamespace.