Skip to content

Commit 8e8987d

Browse files
authored
Merge branch 'main' into copilot/normalize-calltoolresult-content
2 parents 9832a59 + 374f719 commit 8e8987d

2 files changed

Lines changed: 99 additions & 3 deletions

File tree

src/ModelContextProtocol.Core/Client/HttpClientTransportOptions.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,15 @@ public required Uri Endpoint
113113
/// Gets or sets the maximum number of consecutive reconnection attempts when an SSE stream is disconnected.
114114
/// </summary>
115115
/// <value>
116-
/// The maximum number of reconnection attempts. The default is 2.
116+
/// The maximum number of reconnection attempts. The default is 5.
117117
/// </value>
118118
/// <remarks>
119119
/// When an SSE stream is disconnected (e.g., due to a network issue), the client will attempt to
120120
/// reconnect using the Last-Event-ID header to resume from where it left off. This property controls
121-
/// how many reconnection attempts are made before giving up.
121+
/// how many consecutive reconnection attempts are made before giving up. The counter resets to zero
122+
/// on each successful stream read, so this value only limits consecutive failures.
122123
/// </remarks>
123-
public int MaxReconnectionAttempts { get; set; } = 2;
124+
public int MaxReconnectionAttempts { get; set; } = 5;
124125

125126
/// <summary>
126127
/// Gets or sets the default interval at which the client attempts reconnection after an SSE stream is disconnected.

tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthTests.cs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,101 @@ public async Task ResourceMetadata_DoesNotAddTrailingSlash()
893893
transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken);
894894
}
895895

896+
[Fact]
897+
public void CloneResourceMetadataClonesAllProperties()
898+
{
899+
var propertyNames = typeof(ProtectedResourceMetadata).GetProperties().Select(property => property.Name).ToList();
900+
901+
// Set metadata properties to non-default values to verify they're copied.
902+
var metadata = new ProtectedResourceMetadata
903+
{
904+
Resource = "https://example.com/resource",
905+
AuthorizationServers = ["https://auth1.example.com", "https://auth2.example.com"],
906+
BearerMethodsSupported = ["header", "body", "query"],
907+
ScopesSupported = ["read", "write", "admin"],
908+
JwksUri = "https://example.com/.well-known/jwks.json",
909+
ResourceSigningAlgValuesSupported = ["RS256", "ES256"],
910+
ResourceName = "Test Resource",
911+
ResourceDocumentation = "https://docs.example.com",
912+
ResourcePolicyUri = "https://example.com/policy",
913+
ResourceTosUri = "https://example.com/terms",
914+
TlsClientCertificateBoundAccessTokens = true,
915+
AuthorizationDetailsTypesSupported = ["payment_initiation", "account_information"],
916+
DpopSigningAlgValuesSupported = ["RS256", "PS256"],
917+
DpopBoundAccessTokensRequired = true
918+
};
919+
920+
var clonedMetadata = metadata.Clone();
921+
922+
// Ensure the cloned metadata is not the same instance
923+
Assert.NotSame(metadata, clonedMetadata);
924+
925+
// Verify Resource property
926+
Assert.Equal(metadata.Resource, clonedMetadata.Resource);
927+
Assert.True(propertyNames.Remove(nameof(metadata.Resource)));
928+
929+
// Verify AuthorizationServers list is cloned and contains the same values
930+
Assert.NotSame(metadata.AuthorizationServers, clonedMetadata.AuthorizationServers);
931+
Assert.Equal(metadata.AuthorizationServers, clonedMetadata.AuthorizationServers);
932+
Assert.True(propertyNames.Remove(nameof(metadata.AuthorizationServers)));
933+
934+
// Verify BearerMethodsSupported list is cloned and contains the same values
935+
Assert.NotSame(metadata.BearerMethodsSupported, clonedMetadata.BearerMethodsSupported);
936+
Assert.Equal(metadata.BearerMethodsSupported, clonedMetadata.BearerMethodsSupported);
937+
Assert.True(propertyNames.Remove(nameof(metadata.BearerMethodsSupported)));
938+
939+
// Verify ScopesSupported list is cloned and contains the same values
940+
Assert.NotSame(metadata.ScopesSupported, clonedMetadata.ScopesSupported);
941+
Assert.Equal(metadata.ScopesSupported, clonedMetadata.ScopesSupported);
942+
Assert.True(propertyNames.Remove(nameof(metadata.ScopesSupported)));
943+
944+
// Verify JwksUri property
945+
Assert.Equal(metadata.JwksUri, clonedMetadata.JwksUri);
946+
Assert.True(propertyNames.Remove(nameof(metadata.JwksUri)));
947+
948+
// Verify ResourceSigningAlgValuesSupported list is cloned (nullable list)
949+
Assert.NotSame(metadata.ResourceSigningAlgValuesSupported, clonedMetadata.ResourceSigningAlgValuesSupported);
950+
Assert.Equal(metadata.ResourceSigningAlgValuesSupported, clonedMetadata.ResourceSigningAlgValuesSupported);
951+
Assert.True(propertyNames.Remove(nameof(metadata.ResourceSigningAlgValuesSupported)));
952+
953+
// Verify ResourceName property
954+
Assert.Equal(metadata.ResourceName, clonedMetadata.ResourceName);
955+
Assert.True(propertyNames.Remove(nameof(metadata.ResourceName)));
956+
957+
// Verify ResourceDocumentation property
958+
Assert.Equal(metadata.ResourceDocumentation, clonedMetadata.ResourceDocumentation);
959+
Assert.True(propertyNames.Remove(nameof(metadata.ResourceDocumentation)));
960+
961+
// Verify ResourcePolicyUri property
962+
Assert.Equal(metadata.ResourcePolicyUri, clonedMetadata.ResourcePolicyUri);
963+
Assert.True(propertyNames.Remove(nameof(metadata.ResourcePolicyUri)));
964+
965+
// Verify ResourceTosUri property
966+
Assert.Equal(metadata.ResourceTosUri, clonedMetadata.ResourceTosUri);
967+
Assert.True(propertyNames.Remove(nameof(metadata.ResourceTosUri)));
968+
969+
// Verify TlsClientCertificateBoundAccessTokens property
970+
Assert.Equal(metadata.TlsClientCertificateBoundAccessTokens, clonedMetadata.TlsClientCertificateBoundAccessTokens);
971+
Assert.True(propertyNames.Remove(nameof(metadata.TlsClientCertificateBoundAccessTokens)));
972+
973+
// Verify AuthorizationDetailsTypesSupported list is cloned (nullable list)
974+
Assert.NotSame(metadata.AuthorizationDetailsTypesSupported, clonedMetadata.AuthorizationDetailsTypesSupported);
975+
Assert.Equal(metadata.AuthorizationDetailsTypesSupported, clonedMetadata.AuthorizationDetailsTypesSupported);
976+
Assert.True(propertyNames.Remove(nameof(metadata.AuthorizationDetailsTypesSupported)));
977+
978+
// Verify DpopSigningAlgValuesSupported list is cloned (nullable list)
979+
Assert.NotSame(metadata.DpopSigningAlgValuesSupported, clonedMetadata.DpopSigningAlgValuesSupported);
980+
Assert.Equal(metadata.DpopSigningAlgValuesSupported, clonedMetadata.DpopSigningAlgValuesSupported);
981+
Assert.True(propertyNames.Remove(nameof(metadata.DpopSigningAlgValuesSupported)));
982+
983+
// Verify DpopBoundAccessTokensRequired property
984+
Assert.Equal(metadata.DpopBoundAccessTokensRequired, clonedMetadata.DpopBoundAccessTokensRequired);
985+
Assert.True(propertyNames.Remove(nameof(metadata.DpopBoundAccessTokensRequired)));
986+
987+
// Ensure we've checked every property. When new properties get added, we'll have to update this test along with the Clone implementation.
988+
Assert.Empty(propertyNames);
989+
}
990+
896991
[Fact]
897992
public async Task ResourceMetadata_PreservesExplicitTrailingSlash()
898993
{

0 commit comments

Comments
 (0)