diff --git a/docs/list-of-diagnostics.md b/docs/list-of-diagnostics.md
index 515472817..fdf8cece3 100644
--- a/docs/list-of-diagnostics.md
+++ b/docs/list-of-diagnostics.md
@@ -23,7 +23,7 @@ If you use experimental APIs, you will get one of the diagnostics shown below. T
| Diagnostic ID | Description |
| :------------ | :---------- |
-| `MCPEXP001` | Experimental APIs for features in the MCP specification itself, including Tasks and Extensions. Tasks provide a mechanism for asynchronous long-running operations that can be polled for status and results (see [MCP Tasks specification](https://modelcontextprotocol.io/specification/draft/basic/utilities/tasks)). Extensions provide a framework for extending the Model Context Protocol while maintaining interoperability (see [SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)). |
+| `MCPEXP001` | Experimental APIs for features in the MCP specification itself, including Tasks, Extensions, and the `application_type` parameter in Dynamic Client Registration. Tasks provide a mechanism for asynchronous long-running operations that can be polled for status and results (see [MCP Tasks specification](https://modelcontextprotocol.io/specification/draft/basic/utilities/tasks)). Extensions provide a framework for extending the Model Context Protocol while maintaining interoperability (see [SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)). The `application_type` parameter in Dynamic Client Registration is part of a future MCP specification version (see [SEP-837](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/837)). |
| `MCPEXP002` | Experimental SDK APIs unrelated to the MCP specification itself, including subclassing `McpClient`/`McpServer` (see [#1363](https://github.com/modelcontextprotocol/csharp-sdk/pull/1363)) and `RunSessionHandler`, which may be removed or change signatures in a future release (consider using `ConfigureSessionOptions` instead). |
## Obsolete APIs
diff --git a/src/Common/Experimentals.cs b/src/Common/Experimentals.cs
index 7e7e969bb..dec2e05af 100644
--- a/src/Common/Experimentals.cs
+++ b/src/Common/Experimentals.cs
@@ -110,4 +110,23 @@ internal static class Experimentals
/// URL for the experimental RunSessionHandler API.
///
public const string RunSessionHandler_Url = "https://github.com/modelcontextprotocol/csharp-sdk/blob/main/docs/list-of-diagnostics.md#mcpexp002";
+
+ ///
+ /// Diagnostic ID for the experimental application_type parameter in Dynamic Client Registration per SEP-837.
+ ///
+ ///
+ /// This uses the same diagnostic ID as because it is an experimental
+ /// feature in the MCP specification itself.
+ ///
+ public const string DcrApplicationType_DiagnosticId = "MCPEXP001";
+
+ ///
+ /// Message for the experimental application_type parameter in Dynamic Client Registration.
+ ///
+ public const string DcrApplicationType_Message = "The application_type parameter in Dynamic Client Registration is part of a future MCP specification version (SEP-837) and is subject to change.";
+
+ ///
+ /// URL for the experimental application_type parameter in Dynamic Client Registration.
+ ///
+ public const string DcrApplicationType_Url = "https://github.com/modelcontextprotocol/csharp-sdk/blob/main/docs/list-of-diagnostics.md#mcpexp001";
}
diff --git a/src/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs b/src/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs
index ecef8e15e..331778bf3 100644
--- a/src/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs
+++ b/src/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs
@@ -33,11 +33,12 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
private readonly AuthorizationRedirectDelegate _authorizationRedirectDelegate;
private readonly Uri? _clientMetadataDocumentUri;
- // _dcrClientName, _dcrClientUri, _dcrInitialAccessToken and _dcrResponseDelegate are used for dynamic client registration (RFC 7591)
+ // _dcrClientName, _dcrClientUri, _dcrInitialAccessToken, _dcrResponseDelegate and _dcrApplicationType are used for dynamic client registration (RFC 7591)
private readonly string? _dcrClientName;
private readonly Uri? _dcrClientUri;
private readonly string? _dcrInitialAccessToken;
private readonly Func? _dcrResponseDelegate;
+ private readonly string? _dcrApplicationType;
private readonly HttpClient _httpClient;
private readonly ILogger _logger;
@@ -89,6 +90,9 @@ public ClientOAuthProvider(
_dcrClientUri = options.DynamicClientRegistration?.ClientUri;
_dcrInitialAccessToken = options.DynamicClientRegistration?.InitialAccessToken;
_dcrResponseDelegate = options.DynamicClientRegistration?.ResponseDelegate;
+#pragma warning disable MCPEXP001 // application_type in DCR is experimental per SEP-837
+ _dcrApplicationType = options.DynamicClientRegistration?.ApplicationType;
+#pragma warning restore MCPEXP001
_tokenCache = options.TokenCache ?? new InMemoryTokenCache();
}
@@ -654,6 +658,7 @@ private async Task PerformDynamicClientRegistrationAsync(
ClientName = _dcrClientName,
ClientUri = _dcrClientUri?.ToString(),
Scope = GetScopeParameter(protectedResourceMetadata),
+ ApplicationType = _dcrApplicationType ?? (IsLocalhostRedirectUri(_redirectUri) ? "native" : "web"),
};
var requestBytes = JsonSerializer.SerializeToUtf8Bytes(registrationRequest, McpJsonUtilities.JsonContext.Default.DynamicClientRegistrationRequest);
@@ -712,6 +717,10 @@ private async Task PerformDynamicClientRegistrationAsync(
private static string? GetResourceUri(ProtectedResourceMetadata protectedResourceMetadata)
=> protectedResourceMetadata.Resource;
+ private static bool IsLocalhostRedirectUri(Uri redirectUri)
+ => redirectUri.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase)
+ || (System.Net.IPAddress.TryParse(redirectUri.Host, out var ipAddress) && System.Net.IPAddress.IsLoopback(ipAddress));
+
private string? GetScopeParameter(ProtectedResourceMetadata protectedResourceMetadata)
{
if (!string.IsNullOrEmpty(protectedResourceMetadata.WwwAuthenticateScope))
diff --git a/src/ModelContextProtocol.Core/Authentication/DynamicClientRegistrationOptions.cs b/src/ModelContextProtocol.Core/Authentication/DynamicClientRegistrationOptions.cs
index 5d145a568..de378994e 100644
--- a/src/ModelContextProtocol.Core/Authentication/DynamicClientRegistrationOptions.cs
+++ b/src/ModelContextProtocol.Core/Authentication/DynamicClientRegistrationOptions.cs
@@ -1,3 +1,5 @@
+using System.Diagnostics.CodeAnalysis;
+
namespace ModelContextProtocol.Authentication;
///
@@ -46,4 +48,21 @@ public sealed class DynamicClientRegistrationOptions
///
///
public Func? ResponseDelegate { get; set; }
+
+ ///
+ /// Gets or sets the application type to use during dynamic client registration.
+ ///
+ ///
+ ///
+ /// Valid values are "native" and "web". If not specified, the application type will be
+ /// automatically determined based on the redirect URI: "native" for localhost/127.0.0.1
+ /// redirect URIs, "web" for all others.
+ ///
+ ///
+ /// Per the MCP specification, native applications (desktop, mobile, CLI, localhost web apps)
+ /// should use "native", and web applications (remote browser-based) should use "web".
+ ///
+ ///
+ [Experimental(Experimentals.DcrApplicationType_DiagnosticId, UrlFormat = Experimentals.DcrApplicationType_Url)]
+ public string? ApplicationType { get; set; }
}
diff --git a/src/ModelContextProtocol.Core/Authentication/DynamicClientRegistrationRequest.cs b/src/ModelContextProtocol.Core/Authentication/DynamicClientRegistrationRequest.cs
index 8496610e7..69c0797d3 100644
--- a/src/ModelContextProtocol.Core/Authentication/DynamicClientRegistrationRequest.cs
+++ b/src/ModelContextProtocol.Core/Authentication/DynamicClientRegistrationRequest.cs
@@ -48,4 +48,17 @@ internal sealed class DynamicClientRegistrationRequest
///
[JsonPropertyName("scope")]
public string? Scope { get; init; }
+
+ ///
+ /// Gets or sets the application type for the client, as defined in OpenID Connect Dynamic Client Registration 1.0.
+ ///
+ ///
+ /// Valid values are "native" and "web". Per the MCP specification, MCP clients MUST specify an appropriate
+ /// application type during Dynamic Client Registration. This field is automatically populated by the SDK
+ /// based on the redirect URI if not explicitly set via .
+ /// Native applications (desktop, mobile, CLI, localhost web apps) should use "native".
+ /// Web applications (remote browser-based) should use "web".
+ ///
+ [JsonPropertyName("application_type")]
+ public string? ApplicationType { get; init; }
}
\ No newline at end of file
diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthTests.cs
index c4979fb10..24b14c296 100644
--- a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthTests.cs
+++ b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthTests.cs
@@ -1261,4 +1261,84 @@ public async Task CanAuthenticate_WithLegacyServerUsingDefaultEndpointFallback()
await using var client = await McpClient.CreateAsync(
transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken);
}
+
+ [Fact]
+ public async Task DynamicClientRegistration_SendsNativeApplicationType_ForLocalhostRedirectUri()
+ {
+ await using var app = await StartMcpServerAsync();
+
+ await using var transport = new HttpClientTransport(new()
+ {
+ Endpoint = new(McpServerUrl),
+ OAuth = new ClientOAuthOptions()
+ {
+ RedirectUri = new Uri("http://localhost:1179/callback"),
+ AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
+ DynamicClientRegistration = new()
+ {
+ ClientName = "Test MCP Client",
+ },
+ },
+ }, HttpClient, LoggerFactory);
+
+ await using var client = await McpClient.CreateAsync(
+ transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken);
+
+ Assert.Equal("native", TestOAuthServer.LastRegistrationApplicationType);
+ }
+
+ [Fact]
+ public async Task DynamicClientRegistration_SendsWebApplicationType_ForNonLocalhostRedirectUri()
+ {
+ await using var app = await StartMcpServerAsync();
+
+ await using var transport = new HttpClientTransport(new()
+ {
+ Endpoint = new(McpServerUrl),
+ OAuth = new ClientOAuthOptions()
+ {
+ RedirectUri = new Uri("https://myapp.example.com/callback"),
+ AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
+ DynamicClientRegistration = new()
+ {
+ ClientName = "Test MCP Client",
+ },
+ },
+ }, HttpClient, LoggerFactory);
+
+ await using var client = await McpClient.CreateAsync(
+ transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken);
+
+ Assert.Equal("web", TestOAuthServer.LastRegistrationApplicationType);
+ }
+
+ [Fact]
+#pragma warning disable MCPEXP001 // application_type in DCR is experimental per SEP-837
+ public async Task DynamicClientRegistration_UsesExplicitApplicationType_WhenConfigured()
+ {
+ await using var app = await StartMcpServerAsync();
+
+ await using var transport = new HttpClientTransport(new()
+ {
+ Endpoint = new(McpServerUrl),
+ OAuth = new ClientOAuthOptions()
+ {
+ // localhost redirect URI would normally auto-detect as "native",
+ // but the explicit setting should override it.
+ RedirectUri = new Uri("http://localhost:1179/callback"),
+ AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
+ DynamicClientRegistration = new()
+ {
+ ClientName = "Test MCP Client",
+ ApplicationType = "web",
+ },
+ },
+ }, HttpClient, LoggerFactory);
+#pragma warning restore MCPEXP001
+
+ await using var client = await McpClient.CreateAsync(
+ transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken);
+
+ Assert.Equal("web", TestOAuthServer.LastRegistrationApplicationType);
+ }
}
diff --git a/tests/ModelContextProtocol.TestOAuthServer/ClientRegistrationRequest.cs b/tests/ModelContextProtocol.TestOAuthServer/ClientRegistrationRequest.cs
index 50592bbea..62567da94 100644
--- a/tests/ModelContextProtocol.TestOAuthServer/ClientRegistrationRequest.cs
+++ b/tests/ModelContextProtocol.TestOAuthServer/ClientRegistrationRequest.cs
@@ -55,6 +55,12 @@ internal sealed class ClientRegistrationRequest
[JsonPropertyName("scope")]
public string? Scope { get; init; }
+ ///
+ /// Gets or sets the application type.
+ ///
+ [JsonPropertyName("application_type")]
+ public string? ApplicationType { get; init; }
+
///
/// Gets or sets the contacts for the client.
///
diff --git a/tests/ModelContextProtocol.TestOAuthServer/Program.cs b/tests/ModelContextProtocol.TestOAuthServer/Program.cs
index e882ecbef..38748e2f1 100644
--- a/tests/ModelContextProtocol.TestOAuthServer/Program.cs
+++ b/tests/ModelContextProtocol.TestOAuthServer/Program.cs
@@ -81,6 +81,11 @@ public Program(ILoggerProvider? loggerProvider = null, IConnectionListenerFactor
public HashSet DisabledMetadataPaths { get; } = new(StringComparer.OrdinalIgnoreCase);
public IReadOnlyCollection MetadataRequests => _metadataRequests.ToArray();
+ ///
+ /// Gets the application type from the most recent dynamic client registration request received by the server.
+ ///
+ public string? LastRegistrationApplicationType { get; private set; }
+
///
/// Entry point for the application.
///
@@ -501,6 +506,8 @@ IResult HandleMetadataRequest(HttpContext context, string? issuerPath = null)
});
}
+ LastRegistrationApplicationType = registrationRequest.ApplicationType;
+
// Validate redirect URIs are provided
if (registrationRequest.RedirectUris.Count == 0)
{