Conversation
|
/azp run |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
There was a problem hiding this comment.
Pull request overview
This PR addresses MCP stdio clients receiving Method not found: logging/setLevel by adding support for the logging/setLevel JSON-RPC method and wiring it to DAB’s dynamic log-level infrastructure with explicit precedence rules (CLI > config > MCP).
Changes:
- Added
logging/setLeveldispatch + handler inMcpStdioServerto accept the standard MCP request and (when allowed) update runtime log level. - Introduced
ILogLevelControllerand implemented it inDynamicLogLevelProvider, including CLI/config precedence tracking. - Updated CLI engine-launch argument construction to only pass
--LogLevelwhen explicitly provided, plus added unit tests for MCP log-level updates.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Service/Telemetry/DynamicLogLevelProvider.cs | Implements ILogLevelController, adds MCP level mapping and precedence handling (CLI/config/MCP). |
| src/Service/Program.cs | Registers ILogLevelController in DI so MCP can resolve the controller. |
| src/Service.Tests/UnitTests/DynamicLogLevelProviderTests.cs | Adds unit tests for MCP-driven log-level changes and override behavior. |
| src/Core/Telemetry/ILogLevelController.cs | New interface to decouple MCP from the concrete log-level provider. |
| src/Cli/ConfigGenerator.cs | Only forwards --LogLevel when explicitly set by the user (to avoid treating defaults as CLI overrides). |
| src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs | Adds logging/setLevel handler that calls into ILogLevelController. |
| if (!IsCliOverridden) | ||
| { | ||
| CurrentLogLevel = runtimeConfig.GetConfiguredLogLevel(); | ||
|
|
||
| // Track if config explicitly set a log level (not just using defaults) | ||
| IsConfigOverridden = !runtimeConfig.IsLogLevelNull(); | ||
| } |
There was a problem hiding this comment.
IsConfigOverridden is derived from !runtimeConfig.IsLogLevelNull(), but the schema allows runtime.telemetry.log-level values to be null (meaning “use host-mode defaults”). In that case IsLogLevelNull() returns false (because the dictionary exists), causing MCP logging/setLevel to be blocked even though config did not actually pin a log level. Consider treating config as “overridden” only when at least one configured log-level value is non-null (e.g., any entry value != null), or add a dedicated RuntimeConfig helper for this distinction.
| minimumLogLevel = deserializedRuntimeConfig.GetConfiguredLogLevel(); | ||
| HostMode hostModeType = deserializedRuntimeConfig.IsDevelopmentMode() ? HostMode.Development : HostMode.Production; | ||
|
|
||
| _logger.LogInformation($"Setting default minimum LogLevel: {minimumLogLevel} for {hostModeType} mode.", minimumLogLevel, hostModeType); |
There was a problem hiding this comment.
In the default log-level branch, _logger.LogInformation($"Setting default minimum LogLevel: {minimumLogLevel} for {hostModeType} mode.", minimumLogLevel, hostModeType); uses an interpolated string while also passing structured args—those args won’t be captured as structured fields. Use a message template without $"..." (or remove the extra args) so logging behaves as intended.
| _logger.LogInformation($"Setting default minimum LogLevel: {minimumLogLevel} for {hostModeType} mode.", minimumLogLevel, hostModeType); | |
| _logger.LogInformation("Setting default minimum LogLevel: {minimumLogLevel} for {hostModeType} mode.", minimumLogLevel, hostModeType); |
| @@ -2597,17 +2598,22 @@ public static bool TryStartEngineWithOptions(StartOptions options, FileSystemRun | |||
|
|
|||
| minimumLogLevel = (LogLevel)options.LogLevel; | |||
| _logger.LogInformation("Setting minimum LogLevel: {minimumLogLevel}.", minimumLogLevel); | |||
|
|
|||
| // Only add --LogLevel when user explicitly specified it via CLI. | |||
| // This allows MCP logging/setLevel to work when no CLI override is present. | |||
| args.Add("--LogLevel"); | |||
| args.Add(minimumLogLevel.ToString()); | |||
| } | |||
| else | |||
| { | |||
| minimumLogLevel = deserializedRuntimeConfig.GetConfiguredLogLevel(); | |||
| HostMode hostModeType = deserializedRuntimeConfig.IsDevelopmentMode() ? HostMode.Development : HostMode.Production; | |||
|
|
|||
| _logger.LogInformation($"Setting default minimum LogLevel: {minimumLogLevel} for {hostModeType} mode.", minimumLogLevel, hostModeType); | |||
| } | |||
|
|
|||
| args.Add("--LogLevel"); | |||
| args.Add(minimumLogLevel.ToString()); | |||
| // Don't add --LogLevel arg since user didn't explicitly set it. | |||
| // Service will determine default log level based on config or host mode. | |||
| } | |||
There was a problem hiding this comment.
After this change, when options.LogLevel is not provided the CLI no longer passes --LogLevel to the engine. The Service defaults to LogLevel.Error when --LogLevel is absent (Program.GetLogLevelFromCommandLineArgs), so early startup logs will be suppressed even in Development until DynamicLogLevelProvider.UpdateFromRuntimeConfig(...) runs. If the intent is to keep the existing “Debug in Development / Error in Production” default behavior while still allowing MCP to change the level, consider setting the initial log level from the loaded config earlier in host construction (or introducing a non-override mechanism distinct from the CLI override flag).
Aniruddh25
left a comment
There was a problem hiding this comment.
Dont understand how the log level set by MCP tool call takes into effect. Can you show a working demo?
| bool changed = logLevelController.UpdateFromMcp(level); | ||
| if (changed) | ||
| { | ||
| Console.Error.WriteLine($"[MCP DEBUG] Log level changed to: {level}"); |
There was a problem hiding this comment.
Where are these debug messages output? Will those confuse the agent?
Also, what if the LogLevel set by CLI is None. Do these debug messages still be shown? If not, how do you prevent them if you send them to Console.Error.WriteLine?
There was a problem hiding this comment.
Do we really need these console messages?
There was a problem hiding this comment.
No removed them
There was a problem hiding this comment.
The resolution comment says - removed them, but I dont see the change pushed to remote yet. Reopening this comment so that this is not forgotten.
Please resolve a comment only "after" pushing to remote.
There was a problem hiding this comment.
Manual Test 2: CLI override (MCP blocked)
Start MCP server with --LogLevel None
MCP sends logging/setLevel with level: info
Result: Log level stays at None, debug message shows "Log level not changed (CLI override active)"
As far as I understand, there should be no debug message altogether given that LogLevel has been defined as None through CLI.
So, why does the description above say Result shows "Log level not changed"?
Because it was writing directly to stderr. Those [[MCP DEBUG]] messages would still appear because they bypassed ILoggerfiltering and wrote directly to stderr. That's why we removed them. |
| /// Log level precedence (highest to lowest): | ||
| /// 1. CLI --LogLevel flag - cannot be overridden | ||
| /// 2. Config runtime.telemetry.log-level - cannot be overridden by MCP | ||
| /// 3. MCP logging/setLevel - only works if neither CLI nor Config explicitly set a level |
There was a problem hiding this comment.
nit: I would also add as a 4th log level precedence that the log level is set depending on whether DAB is started in Production/Development mode
| /// 2. Config runtime.telemetry.log-level - cannot be overridden by MCP | ||
| /// 3. MCP logging/setLevel - only works if neither CLI nor Config explicitly set a level | ||
| /// | ||
| /// If CLI or Config set the log level, this method accepts the request but silently ignores it. |
There was a problem hiding this comment.
Wouldn't it be better to throw a warning that the request was ignored? I think it would be more beneficial to the user.
| if (logLevelController is null) | ||
| { | ||
| // Log level controller not available - still accept request per MCP spec | ||
| Console.Error.WriteLine("[MCP DEBUG] ILogLevelController not available, logging/setLevel ignored."); |
There was a problem hiding this comment.
Just confirming, this is something that will need to be changed so it is buffered in my PR right?
There was a problem hiding this comment.
Just saw Ani's comment below, will this be removed as well?
| // Only add --LogLevel when user explicitly specified it via CLI. | ||
| // This allows MCP logging/setLevel to work when no CLI override is present. | ||
| args.Add("--LogLevel"); |
There was a problem hiding this comment.
I see that some of the changes in the PR #3420 are included here, I think it would be better to have it only in one, so it is not confusing.
| .ConfigureServices((context, services) => | ||
| { | ||
| services.AddSingleton(LogLevelProvider); | ||
| services.AddSingleton<ILogLevelController>(LogLevelProvider); |
There was a problem hiding this comment.
Why wouldn't we want only 1 signleton?
| { | ||
| /// <summary> | ||
| /// Maps MCP log level strings to Microsoft.Extensions.Logging.LogLevel. | ||
| /// MCP levels: debug, info, notice, warning, error, critical, alert, emergency. |
There was a problem hiding this comment.
Does MCP not have a none log level?
| return false; | ||
| } | ||
|
|
||
| if (string.IsNullOrWhiteSpace(mcpLevel)) |
There was a problem hiding this comment.
From the way that you use this function in the McpStdioServer.cs, the mcpLevel shouldn't even have the possibility of being empty or null. I think it would be better to throw an error for this case.
PR Description
Why make this change?
Closes #3274 - MCP Server returns "Method not found: logging/setLevel" error when clients send the standard MCP logging/setLevel request.
Closes #3275 - Control output in MCP stdio mode (default to
LogLevel.None, redirect/suppress console output).What is this change?
MCP
logging/setLevelHandlerlogging/setLevelJSON-RPC method inMcpStdioServer.csDynamicLogLevelProviderwithILogLevelControllerinterface to allow MCP to update log levels dynamicallyIsCliOverriddenandIsConfigOverriddenproperties to enforce precedence rulesLog Level Precedence System
Precedence (highest to lowest):
--LogLevelflag - cannot be changed by MCPruntime.telemetry.log-level- cannot be changed by MCPlogging/setLevel- only works if neither CLI nor config set a levelIf CLI or config set a level, MCP requests are accepted but silently ignored (no error returned per MCP spec).
Early Config Reading for MCP Mode
TryGetLogLevelFromConfig()inProgram.csto read config file early (before host build)CLI Log Level Handling
Utils.CliLogLevelproperty to track the parsed--LogLevelvalueCustomLoggerProvidernow respects the--LogLevelvalue for its own loggingConfig Helpers
HasExplicitLogLevel()helper toRuntimeConfigto correctly detect when config actually pins a log levelHow was this tested?
DynamicLogLevelProviderTests- 5 tests)Manual Test 1: No override (MCP can change level)
--LogLeveland without configlog-levellogging/setLevelwithlevel: infoManual Test 2: CLI override (MCP blocked)
--LogLevel Warninglogging/setLevelwithlevel: infoManual Test 3: Config override (MCP blocked)
"telemetry": { "log-level": { "default": "Warning" } }to config--LogLevellogging/setLevelwithlevel: infoManual Test 4: Config with null values (MCP can change level)
"telemetry": { "log-level": { "default": null } }to config--LogLevellogging/setLevelwithlevel: infoManual Test 5: CLI
--LogLevel Traceshows verbose logs--LogLevel TraceManual Test 6: CLI
--LogLevel Nonesuppresses all output--LogLevel NoneManual Test 7: Config log level respected at startup
"telemetry": { "log-level": { "default": "Error" } }to config--LogLevelSample Request(s)
MCP client sends:
{ "jsonrpc": "2.0", "id": 1, "method": "logging/setLevel", "params": { "level": "info" } }Server responds with empty result (success per MCP spec) and updates log level if no CLI/config override is active.