Skip to content

Set default log level to None for MCP in Stdio mode.#3420

Open
anushakolan wants to merge 3 commits intodev/anushakolan/set-log-levelfrom
dev/anushakolan/set-default-log-level-none
Open

Set default log level to None for MCP in Stdio mode.#3420
anushakolan wants to merge 3 commits intodev/anushakolan/set-log-levelfrom
dev/anushakolan/set-default-log-level-none

Conversation

@anushakolan
Copy link
Copy Markdown
Contributor

@anushakolan anushakolan commented Apr 8, 2026

PR Description

Why make this change?

Closes #3275 - When --mcp-stdio is used, control the output

When DAB runs as an MCP server with --mcp-stdio, stdout must be reserved exclusively for JSON-RPC protocol messages. Any log output (even informational) causes MCP clients like Claude Desktop to interpret them as protocol violations, displaying warnings like "Discovered 7 tools" mixed with actual tool discovery results.

Related: #3274 - MCP logging/setLevel method not found

What is this change?

This PR implements proper output control for MCP stdio mode:

  1. Default log level is None when --mcp-stdio is used (no CLI or config override)

    • Ensures zero characters are emitted to stdout/stderr by default
    • No log noise interfering with MCP protocol
  2. Output redirection based on log level:

    • LogLevel.None: Both stdout and stderr → TextWriter.Null (zero output)
    • Other levels: stdout → stderr (logs appear on stderr, JSON-RPC stays on stdout)
  3. Log level precedence: CLI --LogLevel > Config runtime.telemetry.log-level > MCP logging/setLevel

    • CLI passes --LogLevelFromConfig flag when config specifies a log level
    • Service correctly identifies the source and blocks MCP override if appropriate
  4. Early logging suppression in CLI before Service starts

    • Added IsMcpStdioMode flag to suppress CLI's own logging in MCP mode

How was this tested?

  • Manual Tests
    • Configured mcp.json to run DAB with --mcp-stdio
    • Verified zero output when no log-level specified (default LogLevel.None)
    • Verified config override by adding "log-level": { "default": "Error" } to runtime.telemetry - logs appeared on stderr
    • Verified CLI override (--LogLevel) takes precedence over config
  • Unit Tests
    • DynamicLogLevelProviderTests (5 tests) - CLI > Config > MCP precedence
    • StartCommandTests (39 tests) - CLI start command functionality

Sample Request(s)

CLI Usage - Default (Zero Output)

dab start --mcp-stdio --config dab-config.json

With no log-level in config, MCP mode produces zero log output.

CLI Usage - Config Override

Config file (dab-config.json):

{
  "runtime": {
    "telemetry": {
      "log-level": {
        "default": "Warning"
      }
    }
  }
}
dab start --mcp-stdio --config dab-config.json

Logs at Warning level appear on stderr; MCP logging/setLevel requests are accepted but ignored.

CLI Usage - CLI Override (Highest Priority)

dab start --mcp-stdio --LogLevel Error --config dab-config.json

Logs at Error level appear on stderr; both config and MCP logging/setLevel are ignored.

MCP logging/setLevel Request

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "logging/setLevel",
  "params": {
    "level": "info"
  }
}

Only changes log level if neither CLI nor config specified a level.

Note

This PR is currently based on dev/anushakolan/set-log-level as it contains dependent changes. The base branch will be updated to main once dev/anushakolan/set-log-level is merged into main.

@anushakolan anushakolan changed the title Dev/anushakolan/set default log level none Set default log level to None for MCP in Stdio mode. Apr 9, 2026
@anushakolan anushakolan changed the base branch from main to dev/anushakolan/set-log-level April 9, 2026 19:46
HostMode hostModeType = deserializedRuntimeConfig.IsDevelopmentMode() ? HostMode.Development : HostMode.Production;
// When --mcp-stdio is used without explicit --LogLevel:
// 1. Check if config has log-level set - use that (Config has priority 2)
// 2. Otherwise default to None for clean MCP stdio output
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also add a comment that says that if we are not using --mcp-stdio it will default to Error for Production mode and Debug for Development mode

if (options.McpStdio)
{
// Check if config explicitly sets a log level
if (!deserializedRuntimeConfig.IsLogLevelNull())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a chance that if we have some LogLevel being set for a specific namespace and this would cause this section to be run even if there is no default value being used. You would need to check specifically if we are using the default value inside the config file.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, might be easier to include the logic to check if we need to use LogLevel none inside the GetConfiguredLogLevel function, since the way it works is that it first checks if we use the config file, and then defaults to the other LogLevels depending on the mode dab is started in.

Comment on lines +2620 to +2621
// This allows MCP logging/setLevel to be blocked by config override.
args.Add("--LogLevel");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to use the --LogLevel flag here, it is causing a bug that I have a PR for #3426

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
// In MCP stdio mode, suppress all CLI logging to keep stdout clean for JSON-RPC.
if (Cli.Utils.IsMcpStdioMode)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need to check if we are overriding the LogLevel from the CLI or the config file or else it would skip outputting the logs even when it is not expected.

Comment thread src/Service/Program.cs
}
else
{
Console.SetOut(Console.Error);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we want to change the Console to only output errors if the LogLevel is overriden by the CLI or the Config file? Wouldn't that defeat the purpose of allowing the LogLevel to be overriden in the first place?

Comment thread src/Service/Program.cs
// Set minimum level at the framework level - this affects all loggers.
// For MCP stdio mode, Console.Out is redirected to stderr in Main(),
// so any logging output goes to stderr and doesn't pollute the JSON-RPC channel.
logging.SetMinimumLevel(LogLevelProvider.CurrentLogLevel);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, does this set the LogLevel for all the possible namespaces? Since the reason we had it the way we did is because the LogLevel was not being applied to some namespaces.

Comment thread src/Service/Program.cs
// Otherwise redirect to stderr so logs don't pollute JSON-RPC.
if (initialLogLevel == LogLevel.None)
{
Console.SetOut(TextWriter.Null);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this change if the LogLevel is intoduced in config file as part of a hot-reload?

Comment thread src/Service/Program.cs
/// Using System.CommandLine Parser to parse args and return
/// the correct log level. We save if there is a log level in args through
/// the out param. For log level out of range we throw an exception.
/// When in MCP stdio mode without explicit --LogLevel, defaults to None without CLI override.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
/// When in MCP stdio mode without explicit --LogLevel, defaults to None without CLI override.
/// When in MCP stdio mode without explicit --LogLevel, defaults to None without CLI or Config override.

Comment thread src/Service/Program.cs
/// </summary>
/// <param name="args">array that may contain log level information.</param>
/// <param name="isLogLevelOverridenByCli">sets if log level is found in the args.</param>
/// <param name="runMcpStdio">whether running in MCP stdio mode.</param>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Capitalize the first letter of all the descriptions for the parameters.

HostMode hostModeType = deserializedRuntimeConfig.IsDevelopmentMode() ? HostMode.Development : HostMode.Production;

_logger.LogInformation($"Setting default minimum LogLevel: {minimumLogLevel} for {hostModeType} mode.", minimumLogLevel, hostModeType);
_logger.LogInformation($"Setting default minimum LogLevel: {minimumLogLevel} for {hostModeType} mode.", minimumLogLevel, hostModeType);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we dont use minimumLogLevel in any way over here in CLI when the --LogLevel argument is absent, why even bother to get the configured log level? See PR #3426 that attempts to fix part of this as well.

Comment thread src/Service/Program.cs
// Initialize log level EARLY, before building the host.
// This ensures logging filters are effective during the entire host build process.
LogLevel initialLogLevel = GetLogLevelFromCommandLineArgs(args, runMcpStdio, out bool isCliOverridden, out bool isConfigOverridden);
LogLevelProvider.SetInitialLogLevel(initialLogLevel, isCliOverridden, isConfigOverridden);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the use of isConfigOverriden flag?

// This allows MCP logging/setLevel to be blocked by config override.
args.Add("--LogLevel");
args.Add(minimumLogLevel.ToString());
args.Add("--LogLevelFromConfig");
Copy link
Copy Markdown
Collaborator

@Aniruddh25 Aniruddh25 Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to add --LogLevelFromConfig for mcp stdio mode, but not otherwise? Wouldnt the Service read from config regardless - what is special about mcp stdio mode that the service cannot read from config and determine the fate of whether to default to None or not?

If we do need provide the loglevel fromconfig for logs sent before Service can read the config, it needs to be done for non-mcp stdio mode as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: When -mcp-stdio is used, control the output

3 participants