Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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("disableCaching")]
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
2 changes: 1 addition & 1 deletion servers/Azure.Mcp.Server/src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ internal static void ConfigureServices(IServiceCollection services)
// 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
2 changes: 1 addition & 1 deletion servers/Template.Mcp.Server/src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ internal static void ConfigureServices(IServiceCollection services)
// stdio-transport-specific implementations of ICacheService.
// The http-transport-specific implementations and configurations must be registered
// within ServiceStartCommand.ExecuteAsync().
services.AddSingleUserCliCacheService();
services.AddSingleUserCliCacheService(true);

foreach (var area in Areas)
{
Expand Down
Loading