Skip to content
Draft
Changes from all 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 @@ -4,6 +4,7 @@
using System.CommandLine.Parsing;
using System.Diagnostics;
using System.Net;
using Azure.Core;
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

The using Azure.Core; directive appears to be unused. The only reference to Azure.Core in the file is a string literal comparison ("Azure.Core.Http") at line 872, which doesn't require this import.

Suggested change
using Azure.Core;

Copilot uses AI. Check for mistakes.
using Azure.Mcp.Core.Areas.Server.Models;
using Azure.Mcp.Core.Areas.Server.Options;
using Azure.Mcp.Core.Commands;
Expand All @@ -21,6 +22,7 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.Identity.Web;
using OpenTelemetry;
using OpenTelemetry.Logs;
Expand Down Expand Up @@ -414,7 +416,8 @@ private IHost CreateHttpHost(ServiceStartOptions serverOptions)
if (!context.Response.HasStarted)
{
HttpRequest request = context.Request;
string resourceMetadataUrl = $"{request.Scheme}://{request.Host}/.well-known/oauth-protected-resource";
string scheme = GetSchemeForOAuthProtectedResourceMetadata(request);
string resourceMetadataUrl = $"{scheme}://{request.Host}/.well-known/oauth-protected-resource";

// Modify the WWW-Authenticate header to include resource_metadata
context.Response.Headers.WWWAuthenticate =
Expand Down Expand Up @@ -498,7 +501,8 @@ private IHost CreateHttpHost(ServiceStartOptions serverOptions)
.GetRequiredService<IOptionsMonitor<MicrosoftIdentityOptions>>();
MicrosoftIdentityOptions azureAdOptions = azureAdOptionsMonitor.Get(JwtBearerDefaults.AuthenticationScheme);
HttpRequest request = context.Request;
string baseUrl = $"{request.Scheme}://{request.Host}";
string scheme = GetSchemeForOAuthProtectedResourceMetadata(request);
string baseUrl = $"{scheme}://{request.Host}";
string? clientId = azureAdOptions.ClientId;
string? tenantId = azureAdOptions.TenantId;
string instance = azureAdOptions.Instance?.TrimEnd('/') ?? "https://login.microsoftonline.com";
Expand Down Expand Up @@ -552,6 +556,50 @@ await JsonSerializer.SerializeAsync(
.AllowAnonymous();

return app;

string GetSchemeForOAuthProtectedResourceMetadata(HttpRequest request)
{
string scheme = request.Scheme;

// Default to "false" for enabling forwarded headers. The env var must be present,
// and it must be parsed to "true".
bool enableForwardedHeaders =
bool.TryParse(
Environment.GetEnvironmentVariable("AZURE_MCP_DANGEROUSLY_ENABLE_FORWARDED_HEADERS"),
out bool parsedEnvVar)
&& parsedEnvVar;

// Azure Container Apps setups usually use HTTP between the ACA platform's
// reverse proxy and the application container. Our OAuth claims challenge
// needs to match what the client will use as a scheme. So only in this
// case do we use the X-Forwarded-Proto header if present. We're also going
// to limit specifically to "http" and "https" values and use their
// lowercase forms rather than the casing in the header.
//
// Other reverse proxies or load balancers may also use X-Forwarded-Proto or
// may use something different. We only special case ACA here because it's
// part of the samples as of 2.0-beta.5. More thorough logic and any
// configuration options can be added later if needed, and that could use
// ASP.NET Core's Forwarded Headers Middleware. See:
// https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer
if (enableForwardedHeaders
&& request.Headers.TryGetValue("X-Forwarded-Proto", out StringValues forwardedProto))
{
if (forwardedProto.FirstOrDefault() is string forwardedProtoValue)
{
if (string.Equals(forwardedProtoValue, "https", StringComparison.OrdinalIgnoreCase))
{
scheme = "https";
}
else if (string.Equals(forwardedProtoValue, "http", StringComparison.OrdinalIgnoreCase))
{
scheme = "http";
}
}
}
Comment on lines +585 to +599
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

These 'if' statements can be combined.

Copilot uses AI. Check for mistakes.

return scheme;
}
Comment on lines +560 to +602
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

The new GetSchemeForOAuthProtectedResourceMetadata local function lacks test coverage. Given that this file has comprehensive unit tests in ServiceStartCommandTests.cs, consider adding tests to cover:

  • Default behavior when AZURE_MCP_DANGEROUSLY_ENABLE_FORWARDED_HEADERS is not set
  • Behavior when the environment variable is set to true and X-Forwarded-Proto header is present with various values (https, http, HTTPS, HTTP, invalid values)
  • Behavior when the environment variable is set to false but the header is present
  • Behavior when the environment variable is set but the header is missing

Copilot uses AI. Check for mistakes.
}

/// <summary>
Expand Down