Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ public void DangerouslyDisableElicitationOption_ParsesCorrectly(bool expectedVal
Assert.Equal(expectedValue, actualValue);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void DisableCachingOption_ParsesCorrectly(bool expectedValue)
{
// Arrange
var parseResult = CreateParseResultWithDisableCaching(expectedValue);

// Act
var actualValue = parseResult.GetValue(ServiceOptionDefinitions.DisableCaching);

// Assert
Assert.Equal(expectedValue, actualValue);
}

[Fact]
public void DangerouslyDisableElicitationOption_DefaultsToFalse()
{
Expand Down Expand Up @@ -807,6 +822,22 @@ private ParseResult CreateParseResultWithDangerouslyDisableElicitation(bool dang
return _command.GetCommand().Parse([.. args]);
}

private ParseResult CreateParseResultWithDisableCaching(bool disableCaching)
{
var args = new List<string>
{
"--transport",
"stdio"
};

if (disableCaching)
{
args.Add("--disable-caching");
}

return _command.GetCommand().Parse([.. args]);
}

private ParseResult CreateParseResultWithTransport(string transport)
{
var args = new List<string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ protected override void RegisterOptions(Command command)
command.Options.Add(ServiceOptionDefinitions.DangerouslyWriteSupportLogsToDir);
command.Options.Add(ServiceOptionDefinitions.DangerouslyDisableRetryLimits);
command.Options.Add(ServiceOptionDefinitions.Cloud);
command.Options.Add(ServiceOptionDefinitions.DisableCaching);
command.Validators.Add(commandResult =>
{
string transport = ResolveTransport(commandResult);
Expand Down Expand Up @@ -178,7 +179,8 @@ protected override ServiceStartOptions BindOptions(ParseResult parseResult)
OutgoingAuthStrategy = outgoingAuthStrategy,
SupportLoggingFolder = parseResult.GetValueOrDefault<string?>(ServiceOptionDefinitions.DangerouslyWriteSupportLogsToDir.Name),
DangerouslyDisableRetryLimits = parseResult.GetValueOrDefault<bool>(ServiceOptionDefinitions.DangerouslyDisableRetryLimits.Name),
Cloud = parseResult.GetValueOrDefault<string?>(ServiceOptionDefinitions.Cloud.Name)
Cloud = parseResult.GetValueOrDefault<string?>(ServiceOptionDefinitions.Cloud.Name),
DisableCaching = parseResult.GetValueOrDefault<bool>(ServiceOptionDefinitions.DisableCaching.Name)
};
return options;
}
Expand Down Expand Up @@ -448,6 +450,7 @@ private IHost CreateStdioHost(ServiceStartOptions serverOptions)
{
// Configure the outgoing authentication strategy.
services.AddSingleIdentityTokenCredentialProvider();
services.AddSingleUserCliCacheService(serverOptions.DisableCaching);

ConfigureServices(services);
ConfigureMcpServer(services, serverOptions);
Expand Down Expand Up @@ -555,8 +558,7 @@ private IHost CreateHttpHost(ServiceStartOptions serverOptions)
}

// Add a multi-user, HTTP context-aware caching strategy to isolate cache entries.
services.AddHttpServiceCacheService();

services.AddHttpServiceCacheService(serverOptions.DisableCaching);

// Configure non-MCP controllers/endpoints/routes/etc.
services.AddHealthChecks();
Expand Down Expand Up @@ -684,7 +686,7 @@ private IHost CreateIncomingAuthDisabledHttpHost(ServiceStartOptions serverOptio
// because we don't yet know what security model we want for this "insecure" mode.
// As a positive, it gives some isolation locally, but that's not a
// design strategy we've fully vetted or endorsed.
services.AddHttpServiceCacheService();
services.AddHttpServiceCacheService(serverOptions.DisableCaching);

WebApplication app = builder.Build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public static class ServiceOptionDefinitions
public const string DangerouslyWriteSupportLogsToDirName = "dangerously-write-support-logs-to-dir";
public const string DangerouslyDisableRetryLimitsName = "dangerously-disable-retry-limits";
public const string CloudName = "cloud";
public const string DisableCachingName = "disable-caching";

public static readonly Option<string> Transport = new($"--{TransportName}")
{
Expand All @@ -25,9 +26,7 @@ public static class ServiceOptionDefinitions
Required = false
};

public static readonly Option<string[]?> Namespace = new(
$"--{NamespaceName}"
)
public static readonly Option<string[]?> Namespace = new($"--{NamespaceName}")
{
Description = "The service namespaces to expose on the MCP server (e.g., storage, keyvault, cosmos).",
Required = false,
Expand All @@ -36,19 +35,15 @@ public static class ServiceOptionDefinitions
DefaultValueFactory = _ => null
};

public static readonly Option<string?> Mode = new Option<string?>(
$"--{ModeName}"
)
public static readonly Option<string?> Mode = new($"--{ModeName}")
{
Description = "Mode for the MCP server. 'single' exposes one azure tool that routes to all services. 'namespace' (default) exposes one tool per service namespace. 'all' exposes all tools individually.",
Required = false,
Arity = ArgumentArity.ZeroOrOne,
DefaultValueFactory = _ => (string?)ModeTypes.NamespaceProxy
};

public static readonly Option<string[]?> Tool = new Option<string[]?>(
$"--{ToolName}"
)
public static readonly Option<string[]?> Tool = new($"--{ToolName}")
{
Description = "Expose only specific tools by name (e.g., 'acr_registry_list'). Repeat this option to include multiple tools, e.g., --tool \"acr_registry_list\" --tool \"group_list\". It automatically switches to \"all\" mode when \"--tool\" is used. It can't be used together with \"--namespace\".",
Required = false,
Expand All @@ -57,65 +52,64 @@ public static class ServiceOptionDefinitions
DefaultValueFactory = _ => null
};

public static readonly Option<bool?> ReadOnly = new(
$"--{ReadOnlyName}")
public static readonly Option<bool?> ReadOnly = new($"--{ReadOnlyName}")
{
Description = "Whether the MCP server should be read-only. If true, no write operations will be allowed.",
DefaultValueFactory = _ => false
};

public static readonly Option<bool> Debug = new(
$"--{DebugName}")
public static readonly Option<bool> Debug = new($"--{DebugName}")
{
Description = "Enable debug mode with verbose logging to stderr.",
DefaultValueFactory = _ => false
};

public static readonly Option<bool> DangerouslyDisableHttpIncomingAuth = new(
$"--{DangerouslyDisableHttpIncomingAuthName}")
public static readonly Option<bool> DangerouslyDisableHttpIncomingAuth = new($"--{DangerouslyDisableHttpIncomingAuthName}")
{
Required = false,
Description = "Dangerously disables HTTP incoming authentication, exposing the server to unauthenticated access over HTTP. Use with extreme caution, this disables all transport security and may expose sensitive data to interception.",
DefaultValueFactory = _ => false
};

public static readonly Option<bool> DangerouslyDisableElicitation = new(
$"--{DangerouslyDisableElicitationName}")
public static readonly Option<bool> DangerouslyDisableElicitation = new($"--{DangerouslyDisableElicitationName}")
{
Required = false,
Description = "Disable elicitation (user confirmation) before allowing high risk commands to run, such as returning Secrets (passwords) from KeyVault.",
DefaultValueFactory = _ => false
};

public static readonly Option<OutgoingAuthStrategy> OutgoingAuthStrategy = new(
$"--{OutgoingAuthStrategyName}")
public static readonly Option<OutgoingAuthStrategy> OutgoingAuthStrategy = new($"--{OutgoingAuthStrategyName}")
{
Required = false,
Description = "Outgoing authentication strategy for service requests. Valid values: NotSet, UseHostingEnvironmentIdentity, UseOnBehalfOf.",
DefaultValueFactory = _ => Options.OutgoingAuthStrategy.NotSet
};

public static readonly Option<string?> DangerouslyWriteSupportLogsToDir = new(
$"--{DangerouslyWriteSupportLogsToDirName}")
public static readonly Option<string?> DangerouslyWriteSupportLogsToDir = new($"--{DangerouslyWriteSupportLogsToDirName}")
{
Required = false,
Description = "Dangerously enables detailed debug-level logging for support and troubleshooting purposes. Specify a folder path where log files will be automatically created with timestamp-based filenames (e.g., azmcp_20251202_143052.log). This may include sensitive information in logs. Use with extreme caution and only when requested by support.",
DefaultValueFactory = _ => null
};

public static readonly Option<bool> DangerouslyDisableRetryLimits = new(
$"--{DangerouslyDisableRetryLimitsName}")
public static readonly Option<bool> DangerouslyDisableRetryLimits = new($"--{DangerouslyDisableRetryLimitsName}")
{
Required = false,
Description = "Dangerously disables upper bounds on retry delays, max delays, network timeouts, and max retries. This may lead to excessively long waits and should only be used when explicitly needed.",
DefaultValueFactory = _ => false
};

public static readonly Option<string?> Cloud = new(
$"--{CloudName}")
public static readonly Option<string?> Cloud = new($"--{CloudName}")
{
Required = false,
Description = "Azure cloud environment for authentication. Valid values: AzureCloud (default), AzureChinaCloud, AzureUSGovernment, or a custom authority host URL starting with https://",
DefaultValueFactory = _ => null
};

public static readonly Option<bool> DisableCaching = new($"--{DisableCachingName}")
{
Required = false,
Description = "Disable caching of resource responses, requiring repeated requests to fetch fresh data each time.",
DefaultValueFactory = _ => false
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,10 @@ public class ServiceStartOptions
/// </summary>
[JsonIgnore]
public bool IsHttpMode => Transport == TransportTypes.Http;

/// <summary>
/// Gets or sets whether caching is disabled.
/// </summary>
[JsonPropertyName("cachingDisabled")]
public bool DisableCaching { get; set; } = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public static class CachingServiceCollectionExtensions
/// <see cref="ServiceLifetime.Singleton"/> into the service collection.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="disabled">Whether caching is disabled.</param>
/// <returns>The service collection.</returns>
/// <remarks>
/// <para>
Expand All @@ -27,9 +28,16 @@ public static class CachingServiceCollectionExtensions
/// It can be overridden as needed by specific configurations.
/// </para>
/// </remarks>
public static IServiceCollection AddSingleUserCliCacheService(this IServiceCollection services)
public static IServiceCollection AddSingleUserCliCacheService(this IServiceCollection services, bool disabled)
{
services.TryAddSingleton<ICacheService, SingleUserCliCacheService>();
if (disabled)
{
services.TryAddSingleton<ICacheService, NoopCacheService>();
}
else
{
services.TryAddSingleton<ICacheService, SingleUserCliCacheService>();
}
return services;
}

Expand All @@ -38,6 +46,7 @@ public static IServiceCollection AddSingleUserCliCacheService(this IServiceColle
/// <see cref="ServiceLifetime.Singleton"/> into the service collection.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="disabled">Whether caching is disabled.</param>
/// <returns>The service collection.</returns>
/// <remarks>
/// <para>
Expand All @@ -49,9 +58,16 @@ public static IServiceCollection AddSingleUserCliCacheService(this IServiceColle
/// This is unlike <see cref="AddSingleUserCliCacheService"/>.
/// </para>
/// </remarks>
public static IServiceCollection AddHttpServiceCacheService(this IServiceCollection services)
public static IServiceCollection AddHttpServiceCacheService(this IServiceCollection services, bool disabled)
{
services.AddSingleton<ICacheService, HttpServiceCacheService>();
if (disabled)
{
services.AddSingleton<ICacheService, NoopCacheService>();
}
else
{
services.AddSingleton<ICacheService, HttpServiceCacheService>();
}
return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Microsoft.Mcp.Core.Services.Caching;

/// <summary>
/// An implementation of <see cref="ICacheService"/> that never caches.
/// </summary>
public class NoopCacheService : ICacheService
{
public ValueTask ClearAsync(CancellationToken cancellationToken) => ValueTask.CompletedTask;

public ValueTask ClearGroupAsync(string group, CancellationToken cancellationToken) => ValueTask.CompletedTask;

public ValueTask DeleteAsync(string group, string key, CancellationToken cancellationToken)
=> ValueTask.CompletedTask;

public ValueTask<T?> GetAsync<T>(string group, string key, TimeSpan? expiration = null, CancellationToken cancellationToken = default)
=> ValueTask.FromResult<T?>(default);

public ValueTask<IEnumerable<string>> GetGroupKeysAsync(string group, CancellationToken cancellationToken)
=> ValueTask.FromResult<IEnumerable<string>>(Array.Empty<string>());

public ValueTask SetAsync<T>(string group, string key, T data, TimeSpan? expiration = null, CancellationToken cancellationToken = default)
=> ValueTask.CompletedTask;
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,13 @@ protected virtual async ValueTask LoadSettingsAsync()
protected virtual async ValueTask InitializeAsyncInternal(TestProxyFixture? proxy = null)
{
await LoadSettingsAsync();
string executablePath = McpTestUtilities.GetAzMcpExecutablePath();

// Use custom arguments if provided, otherwise use standard mode (debug can be enabled via environment variable)
var debugEnvVar = Environment.GetEnvironmentVariable("AZURE_MCP_TEST_DEBUG");
var enableDebug = string.Equals(debugEnvVar, "true", StringComparison.OrdinalIgnoreCase) || Settings.DebugOutput;
List<string> defaultArgs = enableDebug
? ["server", "start", "--mode", "all", "--debug", "--dangerously-disable-elicitation"]
: ["server", "start", "--mode", "all", "--dangerously-disable-elicitation"];
? ["server", "start", "--mode", "all", "--debug", "--dangerously-disable-elicitation", "--disable-caching"]
: ["server", "start", "--mode", "all", "--dangerously-disable-elicitation", "--disable-caching"];
var arguments = CustomArguments?.ToList() ?? defaultArgs;

LiveServerFixture.EnvironmentVariables = GetEnvironmentVariables(proxy);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ public static Process StartHttpServerProcess(
processArguments.Add("--dangerously-disable-http-incoming-auth");
}

var processStartInfo = new System.Diagnostics.ProcessStartInfo(executablePath, string.Join(" ", processArguments))
var processStartInfo = new ProcessStartInfo(executablePath, string.Join(" ", processArguments))
{
RedirectStandardOutput = true,
RedirectStandardError = true,
Expand All @@ -327,7 +327,7 @@ public static Process StartHttpServerProcess(
}
}

var process = System.Diagnostics.Process.Start(processStartInfo);
var process = Process.Start(processStartInfo);

if (process == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public sealed class LiveServerFixture() : IAsyncLifetime
private bool _started;

public Dictionary<string, string?> EnvironmentVariables { get; set; } = new();
public List<string> Arguments { get; set; } = new();
public List<string> Arguments { get; set; } = [];
public ITestOutputHelper? Output { get; set; }
public LiveTestSettings? Settings { get; set; }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
changes:
- section: "Features Added"
description: "Add --disable-caching to server start options to disable caching"
1 change: 1 addition & 0 deletions servers/Azure.Mcp.Server/docs/azmcp-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ The `azmcp server start` command supports the following options:
| `--outgoing-auth-strategy` | No | `NotSet` | Outgoing authentication strategy for service requests. Valid values: `NotSet`, `UseHostingEnvironmentIdentity`, `UseOnBehalfOf`. |
| `--dangerously-write-support-logs-to-dir` | No | - | **⚠️ DANGEROUS**: Enables detailed debug-level logging for support and troubleshooting. Specify a folder path where log files will be created with timestamp-based filenames. May include sensitive information in logs. |
| `--cloud` | No | `AzureCloud` | Azure cloud environment for authentication. Valid values: `AzureCloud` (default), `AzureChinaCloud`, `AzureUSGovernment`, or a custom authority host URL starting with `https://`. When a custom authority host URL is used, only the authentication authority host is changed; ARM and other service endpoints continue to use the Azure public cloud. |
| `--disable-caching` | No | `false` | Disable caching of resource responses, requiring repeated requests to fetch fresh data each time. |

> **⚠️ Security Warning for `--dangerously-disable-elicitation`:**
>
Expand Down
4 changes: 2 additions & 2 deletions servers/Azure.Mcp.Server/src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,12 @@ internal static void ConfigureServices(IServiceCollection services)
services.AddSingleton<ICommandFactory, CommandFactory>();

// !!! WARNING !!!
// stdio-transport-specific implementations of ITenantService and ICacheService.
// stdio-transport-specific implementations of ITenantService.
// The http-transport-specific implementations and configurations must be registered
// within ServiceStartCommand.ExecuteAsync().
services.AddHttpClientServices(configureDefaults: true);
services.AddAzureTenantService();
services.AddSingleUserCliCacheService();
services.AddSingleUserCliCacheService(true);

foreach (var area in Areas)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
changes:
- section: "Features Added"
description: "Add --disable-caching to server start options to disable caching"
4 changes: 2 additions & 2 deletions servers/Fabric.Mcp.Server/src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,11 @@ internal static void ConfigureServices(IServiceCollection services)
services.AddSingleton<ICommandFactory, CommandFactory>();

// !!! WARNING !!!
// stdio-transport-specific implementations of ITenantService and ICacheService.
// stdio-transport-specific implementations of ICacheService.
// The http-transport-specific implementations and configurations must be registered
// within ServiceStartCommand.ExecuteAsync().
services.AddHttpClientServices();
services.AddSingleUserCliCacheService();
services.AddSingleUserCliCacheService(true);

foreach (var area in Areas)
{
Expand Down
Loading
Loading