Skip to content

Commit d5dd345

Browse files
Fix uri primitive lookup
1 parent 153faa3 commit d5dd345

3 files changed

Lines changed: 63 additions & 1 deletion

File tree

src/ModelContextProtocol.Core/Server/McpServerPrimitiveCollection.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@ public class McpServerPrimitiveCollection<T> : ICollection<T>, IReadOnlyCollecti
1010
where T : IMcpServerPrimitive
1111
{
1212
/// <summary>Concurrent dictionary of primitives, indexed by their names.</summary>
13-
private readonly ConcurrentDictionary<string, T> _primitives = [];
13+
private readonly ConcurrentDictionary<string, T> _primitives;
1414

1515
/// <summary>
1616
/// Initializes a new instance of the <see cref="McpServerPrimitiveCollection{T}"/> class.
1717
/// </summary>
1818
public McpServerPrimitiveCollection()
1919
{
20+
_primitives = typeof(T) == typeof(McpServerResource)
21+
? new(UriTemplate.UriTemplateComparer.Instance)
22+
: new();
2023
}
2124

2225
/// <summary>Occurs when the collection is changed.</summary>

src/ModelContextProtocol.Core/UriTemplate.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Buffers;
33
#endif
44
using System.Diagnostics;
5+
using System.Diagnostics.CodeAnalysis;
56
using System.Globalization;
67
using System.Runtime.CompilerServices;
78
using System.Text;
@@ -453,4 +454,52 @@ static void AppendHex(ref DefaultInterpolatedStringHandler builder, char c)
453454
}
454455
}
455456
}
457+
458+
/// <summary>
459+
/// Defines an equality comparer for Uri templates as follows:
460+
/// 1. Non-templated Uris use regular System.Uri equality comparison (host name is case insensitive).
461+
/// 2. Templated Uris use regular string equality.
462+
///
463+
/// We do this because non-templated resources are looked up directly from the resource dictionary
464+
/// and we need to make sure equality is implemented correctly. Templated Uris are resolved using
465+
/// linear traversal so there's no need for equality comparison to be fully accurate.
466+
/// </summary>
467+
public sealed class UriTemplateComparer : IEqualityComparer<string>
468+
{
469+
public static IEqualityComparer<string> Instance { get; } = new UriTemplateComparer();
470+
471+
public bool Equals(string? uriTemplate1, string? uriTemplate2)
472+
{
473+
if (TryParseAsNonTemplatedUri(uriTemplate1, out Uri? uri1) &&
474+
TryParseAsNonTemplatedUri(uriTemplate2, out Uri? uri2))
475+
{
476+
return uri1 == uri2;
477+
}
478+
479+
return string.Equals(uriTemplate1, uriTemplate2, StringComparison.Ordinal);
480+
}
481+
482+
public int GetHashCode([DisallowNull] string uriTemplate)
483+
{
484+
if (TryParseAsNonTemplatedUri(uriTemplate, out Uri? uri))
485+
{
486+
return uri.GetHashCode();
487+
}
488+
else
489+
{
490+
return StringComparer.Ordinal.GetHashCode(uriTemplate);
491+
}
492+
}
493+
494+
private static bool TryParseAsNonTemplatedUri(string? uriTemplate, [NotNullWhen(true)] out Uri? uri)
495+
{
496+
if (uriTemplate is null || uriTemplate.Contains('{'))
497+
{
498+
uri = null;
499+
return false;
500+
}
501+
502+
return Uri.TryCreate(uriTemplate, UriKind.RelativeOrAbsolute, out uri);
503+
}
504+
}
456505
}

tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,16 @@ public async Task UriTemplate_IsHostCaseInsensitive(string actualUri, string que
280280
TestContext.Current.CancellationToken));
281281
}
282282

283+
[Fact]
284+
public async Task ResourceCollection_UsesCaseInsensitiveHostLookup()
285+
{
286+
McpServerResource t1 = McpServerResource.Create(() => "resource", new() { UriTemplate = "resource://MyCoolResource" });
287+
McpServerResource t2 = McpServerResource.Create(() => "resource", new() { UriTemplate = "resource://MyCoolResource2" });
288+
McpServerPrimitiveCollection<McpServerResource> collection = new() { t1, t2 };
289+
Assert.True(collection.TryGetPrimitive("resource://mycoolresource", out McpServerResource? result));
290+
Assert.Same(t1, result);
291+
}
292+
283293
[Fact]
284294
public void MimeType_DefaultsToOctetStream()
285295
{

0 commit comments

Comments
 (0)