Fix URI template derivation and lookup (#530)

* Use lowercase host in resource URI template derivation.

* Address feedback

* Add casing testing for non-templated resource lookups.

* Fix uri primitive lookup

* Use a hardcoded mcp hostname in derived resource template URIs and ensure built-in resources work with the VS Code client.

* Address feedback

* Fix equality comparison

* Use dedicated type for resource collections.
This commit is contained in:
Eirik Tsarpalis 2025-06-23 19:49:38 +03:00 • committed by GitHub
commit 0b26b7bf22
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 174 additions and 75 deletions

View file

@ -2,12 +2,14 @@ using OpenTelemetry;
using OpenTelemetry.Metrics; using OpenTelemetry.Metrics;
using OpenTelemetry.Trace; using OpenTelemetry.Trace;
using TestServerWithHosting.Tools; using TestServerWithHosting.Tools;
using TestServerWithHosting.Resources;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMcpServer() builder.Services.AddMcpServer()
.WithHttpTransport() .WithHttpTransport()
.WithTools<EchoTool>() .WithTools<EchoTool>()
.WithTools<SampleLlmTool>(); .WithTools<SampleLlmTool>()
.WithResources<SimpleResourceType>();
builder.Services.AddOpenTelemetry() builder.Services.AddOpenTelemetry()
.WithTracing(b => b.AddSource("*") .WithTracing(b => b.AddSource("*")

View file

@ -0,0 +1,12 @@
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.ComponentModel;
namespace TestServerWithHosting.Resources;
[McpServerResourceType]
public class SimpleResourceType
{
[McpServerResource, Description("A direct text resource")]
public static string DirectTextResource() => "This is a direct resource";
}

View file

@ -103,5 +103,5 @@ public sealed class ResourcesCapability
/// </para> /// </para>
/// </remarks> /// </remarks>
[JsonIgnore] [JsonIgnore]
public McpServerPrimitiveCollection<McpServerResource>? ResourceCollection { get; set; } public McpServerResourceCollection? ResourceCollection { get; set; }
} }

View file

@ -264,7 +264,7 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
Name = name, Name = name,
Title = options?.Title, Title = options?.Title,
Description = options?.Description, Description = options?.Description,
MimeType = options?.MimeType, MimeType = options?.MimeType ?? "application/octet-stream",
}; };
return new AIFunctionMcpServerResource(function, resource); return new AIFunctionMcpServerResource(function, resource);
@ -295,7 +295,7 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
{ {
StringBuilder template = new(); StringBuilder template = new();
template.Append("resource://").Append(Uri.EscapeDataString(name)); template.Append("resource://mcp/").Append(Uri.EscapeDataString(name));
if (function.JsonSchema.TryGetProperty("properties", out JsonElement properties)) if (function.JsonSchema.TryGetProperty("properties", out JsonElement properties))
{ {
@ -359,7 +359,7 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
return null; return null;
} }
} }
else if (request.Params.Uri != ProtocolResource!.Uri) else if (!UriTemplate.UriTemplateComparer.Instance.Equals(request.Params.Uri, ProtocolResource!.Uri))
{ {
return null; return null;
} }

View file

@ -228,7 +228,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
var unsubscribeHandler = resourcesCapability.UnsubscribeFromResourcesHandler ?? (static async (_, __) => new EmptyResult()); var unsubscribeHandler = resourcesCapability.UnsubscribeFromResourcesHandler ?? (static async (_, __) => new EmptyResult());
var resources = resourcesCapability.ResourceCollection; var resources = resourcesCapability.ResourceCollection;
var listChanged = resourcesCapability.ListChanged; var listChanged = resourcesCapability.ListChanged;
var subcribe = resourcesCapability.Subscribe; var subscribe = resourcesCapability.Subscribe;
// Handle resources provided via DI. // Handle resources provided via DI.
if (resources is { IsEmpty: false }) if (resources is { IsEmpty: false })
@ -309,7 +309,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
listChanged = true; listChanged = true;
// TODO: Implement subscribe/unsubscribe logic for resource and resource template collections. // TODO: Implement subscribe/unsubscribe logic for resource and resource template collections.
// subcribe = true; // subscribe = true;
} }
ServerCapabilities.Resources.ListResourcesHandler = listResourcesHandler; ServerCapabilities.Resources.ListResourcesHandler = listResourcesHandler;
@ -319,7 +319,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
ServerCapabilities.Resources.SubscribeToResourcesHandler = subscribeHandler; ServerCapabilities.Resources.SubscribeToResourcesHandler = subscribeHandler;
ServerCapabilities.Resources.UnsubscribeFromResourcesHandler = unsubscribeHandler; ServerCapabilities.Resources.UnsubscribeFromResourcesHandler = unsubscribeHandler;
ServerCapabilities.Resources.ListChanged = listChanged; ServerCapabilities.Resources.ListChanged = listChanged;
ServerCapabilities.Resources.Subscribe = subcribe; ServerCapabilities.Resources.Subscribe = subscribe;
SetHandler( SetHandler(
RequestMethods.ResourcesList, RequestMethods.ResourcesList,

View file

@ -10,13 +10,14 @@ public class McpServerPrimitiveCollection<T> : ICollection<T>, IReadOnlyCollecti
where T : IMcpServerPrimitive where T : IMcpServerPrimitive
{ {
/// <summary>Concurrent dictionary of primitives, indexed by their names.</summary> /// <summary>Concurrent dictionary of primitives, indexed by their names.</summary>
private readonly ConcurrentDictionary<string, T> _primitives = []; private readonly ConcurrentDictionary<string, T> _primitives;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="McpServerPrimitiveCollection{T}"/> class. /// Initializes a new instance of the <see cref="McpServerPrimitiveCollection{T}"/> class.
/// </summary> /// </summary>
public McpServerPrimitiveCollection() public McpServerPrimitiveCollection(IEqualityComparer<string>? keyComparer = null)
{ {
_primitives = new(keyComparer);
} }
/// <summary>Occurs when the collection is changed.</summary> /// <summary>Occurs when the collection is changed.</summary>

View file

@ -0,0 +1,5 @@
namespace ModelContextProtocol.Server;
/// <summary>Provides a thread-safe collection of <see cref="McpServerResource"/> instances, indexed by their URI templates.</summary>
public sealed class McpServerResourceCollection()
: McpServerPrimitiveCollection<McpServerResource>(UriTemplate.UriTemplateComparer.Instance);

View file

@ -2,6 +2,7 @@
using System.Buffers; using System.Buffers;
#endif #endif
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
@ -453,4 +454,53 @@ internal static partial class UriTemplate
} }
} }
} }
/// <summary>
/// Defines an equality comparer for Uri templates as follows:
/// 1. Non-templated Uris use regular System.Uri equality comparison (host name is case insensitive).
/// 2. Templated Uris use regular string equality.
///
/// We do this because non-templated resources are looked up directly from the resource dictionary
/// and we need to make sure equality is implemented correctly. Templated Uris are resolved in a
/// fallback step using linear traversal of the resource dictionary, so their equality is only
/// there to distinguish between different templates.
/// </summary>
public sealed class UriTemplateComparer : IEqualityComparer<string>
{
public static IEqualityComparer<string> Instance { get; } = new UriTemplateComparer();
public bool Equals(string? uriTemplate1, string? uriTemplate2)
{
if (TryParseAsNonTemplatedUri(uriTemplate1, out Uri? uri1) &&
TryParseAsNonTemplatedUri(uriTemplate2, out Uri? uri2))
{
return uri1 == uri2;
}
return string.Equals(uriTemplate1, uriTemplate2, StringComparison.Ordinal);
}
public int GetHashCode([DisallowNull] string uriTemplate)
{
if (TryParseAsNonTemplatedUri(uriTemplate, out Uri? uri))
{
return uri.GetHashCode();
}
else
{
return StringComparer.Ordinal.GetHashCode(uriTemplate);
}
}
private static bool TryParseAsNonTemplatedUri(string? uriTemplate, [NotNullWhen(true)] out Uri? uri)
{
if (uriTemplate is null || uriTemplate.Contains('{'))
{
uri = null;
return false;
}
return Uri.TryCreate(uriTemplate, UriKind.Absolute, out uri);
}
}
} }

View file

@ -63,7 +63,7 @@ internal sealed class McpServerOptionsSetup(
// a collection, add to it, otherwise create a new one. We want to maintain the identity // a collection, add to it, otherwise create a new one. We want to maintain the identity
// of an existing collection in case someone has provided their own derived type, wants // of an existing collection in case someone has provided their own derived type, wants
// change notifications, etc. // change notifications, etc.
McpServerPrimitiveCollection<McpServerResource> resourceCollection = options.Capabilities?.Resources?.ResourceCollection ?? []; McpServerResourceCollection resourceCollection = options.Capabilities?.Resources?.ResourceCollection ?? [];
foreach (var resource in serverResources) foreach (var resource in serverResources)
{ {
resourceCollection.TryAdd(resource); resourceCollection.TryAdd(resource);

View file

@ -220,7 +220,7 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
await using IMcpClient client = await CreateMcpClientForServer(); await using IMcpClient client = await CreateMcpClientForServer();
await Assert.ThrowsAsync<McpException>(async () => await client.ReadResourceAsync( await Assert.ThrowsAsync<McpException>(async () => await client.ReadResourceAsync(
$"resource://{nameof(SimpleResources.ThrowsException)}", $"resource://mcp/{nameof(SimpleResources.ThrowsException)}",
cancellationToken: TestContext.Current.CancellationToken)); cancellationToken: TestContext.Current.CancellationToken));
} }
@ -230,7 +230,7 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
await using IMcpClient client = await CreateMcpClientForServer(); await using IMcpClient client = await CreateMcpClientForServer();
var e = await Assert.ThrowsAsync<McpException>(async () => await client.ReadResourceAsync( var e = await Assert.ThrowsAsync<McpException>(async () => await client.ReadResourceAsync(
"test://NotRegisteredResource", "test:///NotRegisteredResource",
cancellationToken: TestContext.Current.CancellationToken)); cancellationToken: TestContext.Current.CancellationToken));
Assert.Contains("Resource not found", e.Message); Assert.Contains("Resource not found", e.Message);
@ -268,8 +268,8 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
sc.AddMcpServer().WithResourcesFromAssembly(); sc.AddMcpServer().WithResourcesFromAssembly();
IServiceProvider services = sc.BuildServiceProvider(); IServiceProvider services = sc.BuildServiceProvider();
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResource?.Uri == $"resource://{nameof(SimpleResources.SomeNeatDirectResource)}"); Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResource?.Uri == $"resource://mcp/{nameof(SimpleResources.SomeNeatDirectResource)}");
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResourceTemplate?.UriTemplate == $"resource://{nameof(SimpleResources.SomeNeatTemplatedResource)}{{?name}}"); Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResourceTemplate?.UriTemplate == $"resource://mcp/{nameof(SimpleResources.SomeNeatTemplatedResource)}{{?name}}");
} }
[Fact] [Fact]
@ -279,13 +279,13 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
sc.AddMcpServer() sc.AddMcpServer()
.WithResources<SimpleResources>() .WithResources<SimpleResources>()
.WithResources<MoreResources>() .WithResources<MoreResources>()
.WithResources([McpServerResource.Create(() => "42", new() { UriTemplate = "myResources://Returns42/{something}" })]); .WithResources([McpServerResource.Create(() => "42", new() { UriTemplate = "myResources:///returns42/{something}" })]);
IServiceProvider services = sc.BuildServiceProvider(); IServiceProvider services = sc.BuildServiceProvider();
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResource?.Uri == $"resource://{nameof(SimpleResources.SomeNeatDirectResource)}"); Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResource?.Uri == $"resource://mcp/{nameof(SimpleResources.SomeNeatDirectResource)}");
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResourceTemplate?.UriTemplate == $"resource://{nameof(SimpleResources.SomeNeatTemplatedResource)}{{?name}}"); Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResourceTemplate?.UriTemplate == $"resource://mcp/{nameof(SimpleResources.SomeNeatTemplatedResource)}{{?name}}");
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResourceTemplate?.UriTemplate == $"resource://{nameof(MoreResources.AnotherNeatDirectResource)}"); Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResourceTemplate?.UriTemplate == $"resource://mcp/{nameof(MoreResources.AnotherNeatDirectResource)}");
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResourceTemplate.UriTemplate == "myResources://Returns42/{something}"); Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResourceTemplate.UriTemplate == "myResources:///returns42/{something}");
} }
[McpServerResourceType] [McpServerResourceType]

View file

@ -122,159 +122,188 @@ public partial class McpServerResourceTests
public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported() public async Task UriTemplate_CreatedFromParameters_LotsOfTypesSupported()
{ {
const string Name = "Hello"; const string Name = "Hello";
McpServerResource t; McpServerResource t;
ReadResourceResult? result; ReadResourceResult? result;
IMcpServer server = new Mock<IMcpServer>().Object; IMcpServer server = new Mock<IMcpServer>().Object;
t = McpServerResource.Create(() => "42", new() { Name = Name }); t = McpServerResource.Create(() => "42", new() { Name = Name });
Assert.Equal($"resource://{Name}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal("resource://mcp/Hello", t.ProtocolResourceTemplate.UriTemplate);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://{Name}" } }, new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = "resource://mcp/Hello" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text);
t = McpServerResource.Create((IMcpServer server) => "42", new() { Name = Name }); t = McpServerResource.Create((IMcpServer server) => "42", new() { Name = Name });
Assert.Equal($"resource://{Name}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal("resource://mcp/Hello", t.ProtocolResourceTemplate.UriTemplate);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://{Name}" } }, new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = "resource://mcp/Hello" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text);
t = McpServerResource.Create((string arg1) => arg1, new() { Name = Name }); t = McpServerResource.Create((string arg1) => arg1, new() { Name = Name });
Assert.Equal($"resource://{Name}{{?arg1}}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal($"resource://mcp/Hello{{?arg1}}", t.ProtocolResourceTemplate.UriTemplate);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://{Name}?arg1=wOrLd" } }, new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?arg1=wOrLd" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("wOrLd", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("wOrLd", ((TextResourceContents)result.Contents[0]).Text);
t = McpServerResource.Create((string arg1, string? arg2 = null) => arg1 + arg2, new() { Name = Name }); t = McpServerResource.Create((string arg1, string? arg2 = null) => arg1 + arg2, new() { Name = Name });
Assert.Equal($"resource://{Name}{{?arg1,arg2}}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal($"resource://mcp/Hello{{?arg1,arg2}}", t.ProtocolResourceTemplate.UriTemplate);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://{Name}?arg1=wo&arg2=rld" } }, new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?arg1=wo&arg2=rld" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("world", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("world", ((TextResourceContents)result.Contents[0]).Text);
t = McpServerResource.Create((object a1, bool a2, char a3, byte a4, sbyte a5) => a1.ToString() + a2 + a3 + a4 + a5, new() { Name = Name }); t = McpServerResource.Create((object a1, bool a2, char a3, byte a4, sbyte a5) => a1.ToString() + a2 + a3 + a4 + a5, new() { Name = Name });
Assert.Equal($"resource://{Name}{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://{Name}?a1=hi&a2=true&a3=s&a4=12&a5=34" } }, new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1=hi&a2=true&a3=s&a4=12&a5=34" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("hiTrues1234", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("hiTrues1234", ((TextResourceContents)result.Contents[0]).Text);
t = McpServerResource.Create((ushort a1, short a2, uint a3, int a4, ulong a5) => (a1 + a2 + a3 + a4 + (long)a5).ToString(), new() { Name = Name }); t = McpServerResource.Create((ushort a1, short a2, uint a3, int a4, ulong a5) => (a1 + a2 + a3 + a4 + (long)a5).ToString(), new() { Name = Name });
Assert.Equal($"resource://{Name}{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://{Name}?a1=10&a2=20&a3=30&a4=40&a5=50" } }, new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1=10&a2=20&a3=30&a4=40&a5=50" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("150", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("150", ((TextResourceContents)result.Contents[0]).Text);
t = McpServerResource.Create((long a1, float a2, double a3, decimal a4, TimeSpan a5) => a5.ToString(), new() { Name = Name }); t = McpServerResource.Create((long a1, float a2, double a3, decimal a4, TimeSpan a5) => a5.ToString(), new() { Name = Name });
Assert.Equal($"resource://{Name}{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://{Name}?a1=1&a2=2&a3=3&a4=4&a5=5" } }, new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1=1&a2=2&a3=3&a4=4&a5=5" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("5.00:00:00", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("5.00:00:00", ((TextResourceContents)result.Contents[0]).Text);
t = McpServerResource.Create((DateTime a1, DateTimeOffset a2, Uri a3, Guid a4, Version a5) => a4.ToString("N") + a5, new() { Name = Name }); t = McpServerResource.Create((DateTime a1, DateTimeOffset a2, Uri a3, Guid a4, Version a5) => a4.ToString("N") + a5, new() { Name = Name });
Assert.Equal($"resource://{Name}{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://{Name}?a1={DateTime.UtcNow:r}&a2={DateTimeOffset.UtcNow:r}&a3=http%3A%2F%2Ftest&a4=14e5f43d-0d41-47d6-8207-8249cf669e41&a5=1.2.3.4" } }, new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1={DateTime.UtcNow:r}&a2={DateTimeOffset.UtcNow:r}&a3=http%3A%2F%2Ftest&a4=14e5f43d-0d41-47d6-8207-8249cf669e41&a5=1.2.3.4" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("14e5f43d0d4147d682078249cf669e411.2.3.4", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("14e5f43d0d4147d682078249cf669e411.2.3.4", ((TextResourceContents)result.Contents[0]).Text);
t = McpServerResource.Create((Half a2, Int128 a3, UInt128 a4, IntPtr a5) => (a3 + (Int128)a4 + a5).ToString(), new() { Name = Name }); t = McpServerResource.Create((Half a2, Int128 a3, UInt128 a4, IntPtr a5) => (a3 + (Int128)a4 + a5).ToString(), new() { Name = Name });
Assert.Equal($"resource://{Name}{{?a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal($"resource://mcp/Hello{{?a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://{Name}?a2=1.0&a3=3&a4=4&a5=5" } }, new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a2=1.0&a3=3&a4=4&a5=5" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("12", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("12", ((TextResourceContents)result.Contents[0]).Text);
t = McpServerResource.Create((UIntPtr a1, DateOnly a2, TimeOnly a3) => a1.ToString(), new() { Name = Name }); t = McpServerResource.Create((UIntPtr a1, DateOnly a2, TimeOnly a3) => a1.ToString(), new() { Name = Name });
Assert.Equal($"resource://{Name}{{?a1,a2,a3}}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3}}", t.ProtocolResourceTemplate.UriTemplate);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://{Name}?a1=123&a2=0001-02-03&a3=01%3A02%3A03" } }, new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1=123&a2=0001-02-03&a3=01%3A02%3A03" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("123", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("123", ((TextResourceContents)result.Contents[0]).Text);
t = McpServerResource.Create((bool? a2, char? a3, byte? a4, sbyte? a5) => a2?.ToString() + a3 + a4 + a5, new() { Name = Name }); t = McpServerResource.Create((bool? a2, char? a3, byte? a4, sbyte? a5) => a2?.ToString() + a3 + a4 + a5, new() { Name = Name });
Assert.Equal($"resource://{Name}{{?a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal($"resource://mcp/Hello{{?a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://{Name}?a2=true&a3=s&a4=12&a5=34" } }, new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a2=true&a3=s&a4=12&a5=34" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("Trues1234", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("Trues1234", ((TextResourceContents)result.Contents[0]).Text);
t = McpServerResource.Create((ushort? a1, short? a2, uint? a3, int? a4, ulong? a5) => (a1 + a2 + a3 + a4 + (long?)a5).ToString(), new() { Name = Name }); t = McpServerResource.Create((ushort? a1, short? a2, uint? a3, int? a4, ulong? a5) => (a1 + a2 + a3 + a4 + (long?)a5).ToString(), new() { Name = Name });
Assert.Equal($"resource://{Name}{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://{Name}?a1=10&a2=20&a3=30&a4=40&a5=50" } }, new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1=10&a2=20&a3=30&a4=40&a5=50" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("150", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("150", ((TextResourceContents)result.Contents[0]).Text);
t = McpServerResource.Create((long? a1, float? a2, double? a3, decimal? a4, TimeSpan? a5) => a5?.ToString(), new() { Name = Name }); t = McpServerResource.Create((long? a1, float? a2, double? a3, decimal? a4, TimeSpan? a5) => a5?.ToString(), new() { Name = Name });
Assert.Equal($"resource://{Name}{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://{Name}?a1=1&a2=2&a3=3&a4=4&a5=5" } }, new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1=1&a2=2&a3=3&a4=4&a5=5" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("5.00:00:00", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("5.00:00:00", ((TextResourceContents)result.Contents[0]).Text);
t = McpServerResource.Create((DateTime? a1, DateTimeOffset? a2, Guid? a4) => a4?.ToString("N"), new() { Name = Name }); t = McpServerResource.Create((DateTime? a1, DateTimeOffset? a2, Guid? a4) => a4?.ToString("N"), new() { Name = Name });
Assert.Equal($"resource://{Name}{{?a1,a2,a4}}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a4}}", t.ProtocolResourceTemplate.UriTemplate);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://{Name}?a1={DateTime.UtcNow:r}&a2={DateTimeOffset.UtcNow:r}&a4=14e5f43d-0d41-47d6-8207-8249cf669e41" } }, new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1={DateTime.UtcNow:r}&a2={DateTimeOffset.UtcNow:r}&a4=14e5f43d-0d41-47d6-8207-8249cf669e41" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("14e5f43d0d4147d682078249cf669e41", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("14e5f43d0d4147d682078249cf669e41", ((TextResourceContents)result.Contents[0]).Text);
t = McpServerResource.Create((Half? a2, Int128? a3, UInt128? a4, IntPtr? a5) => (a3 + (Int128?)a4 + a5).ToString(), new() { Name = Name }); t = McpServerResource.Create((Half? a2, Int128? a3, UInt128? a4, IntPtr? a5) => (a3 + (Int128?)a4 + a5).ToString(), new() { Name = Name });
Assert.Equal($"resource://{Name}{{?a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal($"resource://mcp/Hello{{?a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://{Name}?a2=1.0&a3=3&a4=4&a5=5" } }, new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a2=1.0&a3=3&a4=4&a5=5" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("12", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("12", ((TextResourceContents)result.Contents[0]).Text);
t = McpServerResource.Create((UIntPtr? a1, DateOnly? a2, TimeOnly? a3) => a1?.ToString(), new() { Name = Name }); t = McpServerResource.Create((UIntPtr? a1, DateOnly? a2, TimeOnly? a3) => a1?.ToString(), new() { Name = Name });
Assert.Equal($"resource://{Name}{{?a1,a2,a3}}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3}}", t.ProtocolResourceTemplate.UriTemplate);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://{Name}?a1=123&a2=0001-02-03&a3=01%3A02%3A03" } }, new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1=123&a2=0001-02-03&a3=01%3A02%3A03" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("123", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("123", ((TextResourceContents)result.Contents[0]).Text);
} }
[Theory] [Theory]
[InlineData("resource://Hello?arg1=42&arg2=84")] [InlineData("resource://mcp/Hello?arg1=42&arg2=84")]
[InlineData("resource://Hello?arg1=42&arg2=84&arg3=123")] [InlineData("resource://mcp/Hello?arg1=42&arg2=84&arg3=123")]
[InlineData("resource://Hello#fragment")] [InlineData("resource://mcp/Hello#fragment")]
public async Task UriTemplate_NonMatchingUri_ReturnsNull(string uri) public async Task UriTemplate_NonMatchingUri_ReturnsNull(string uri)
{ {
McpServerResource t = McpServerResource.Create((string arg1) => arg1, new() { Name = "Hello" }); McpServerResource t = McpServerResource.Create((string arg1) => arg1, new() { Name = "Hello" });
Assert.Equal("resource://Hello{?arg1}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal("resource://mcp/Hello{?arg1}", t.ProtocolResourceTemplate.UriTemplate);
Assert.Null(await t.ReadAsync( Assert.Null(await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = uri } }, new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = uri } },
TestContext.Current.CancellationToken)); TestContext.Current.CancellationToken));
} }
[Theory] [Theory]
[InlineData("resource://Hello?arg1=test")] [InlineData("resource://MyCoolResource", "resource://mycoolresource")]
[InlineData("resource://Hello?arg2=test")] [InlineData("resource://MyCoolResource{?arg1}", "resource://mycoolresource?arg1=42")]
public async Task UriTemplate_IsHostCaseInsensitive(string actualUri, string queriedUri)
{
McpServerResource t = McpServerResource.Create(() => "resource", new() { UriTemplate = actualUri });
Assert.NotNull(await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = queriedUri } },
TestContext.Current.CancellationToken));
}
[Fact]
public async Task ResourceCollection_UsesCaseInsensitiveHostLookup()
{
McpServerResource t1 = McpServerResource.Create(() => "resource", new() { UriTemplate = "resource://MyCoolResource" });
McpServerResource t2 = McpServerResource.Create(() => "resource", new() { UriTemplate = "resource://MyCoolResource2" });
McpServerResourceCollection collection = new() { t1, t2 };
Assert.True(collection.TryGetPrimitive("resource://mycoolresource", out McpServerResource? result));
Assert.Same(t1, result);
}
[Fact]
public void MimeType_DefaultsToOctetStream()
{
McpServerResource t = McpServerResource.Create(() => "resource", new() { Name = "My Cool Resource" });
Assert.Equal("application/octet-stream", t.ProtocolResourceTemplate.MimeType);
}
[Theory]
[InlineData("resource://mcp/Hello?arg1=test")]
[InlineData("resource://mcp/Hello?arg2=test")]
public async Task UriTemplate_MissingParameter_Throws(string uri) public async Task UriTemplate_MissingParameter_Throws(string uri)
{ {
McpServerResource t = McpServerResource.Create((string arg1, int arg2) => arg1, new() { Name = "Hello" }); McpServerResource t = McpServerResource.Create((string arg1, int arg2) => arg1, new() { Name = "Hello" });
Assert.Equal("resource://Hello{?arg1,arg2}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal("resource://mcp/Hello{?arg1,arg2}", t.ProtocolResourceTemplate.UriTemplate);
await Assert.ThrowsAsync<ArgumentException>(async () => await t.ReadAsync( await Assert.ThrowsAsync<ArgumentException>(async () => await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = uri } }, new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = uri } },
TestContext.Current.CancellationToken)); TestContext.Current.CancellationToken));
@ -284,30 +313,30 @@ public partial class McpServerResourceTests
public async Task UriTemplate_MissingOptionalParameter_Succeeds() public async Task UriTemplate_MissingOptionalParameter_Succeeds()
{ {
McpServerResource t = McpServerResource.Create((string? arg1 = null, int? arg2 = null) => arg1 + arg2, new() { Name = "Hello" }); McpServerResource t = McpServerResource.Create((string? arg1 = null, int? arg2 = null) => arg1 + arg2, new() { Name = "Hello" });
Assert.Equal("resource://Hello{?arg1,arg2}", t.ProtocolResourceTemplate.UriTemplate); Assert.Equal("resource://mcp/Hello{?arg1,arg2}", t.ProtocolResourceTemplate.UriTemplate);
ReadResourceResult? result; ReadResourceResult? result;
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = "resource://Hello" } }, new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = "resource://mcp/Hello" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("", ((TextResourceContents)result.Contents[0]).Text);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = "resource://Hello?arg1=first" } }, new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = "resource://mcp/Hello?arg1=first" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("first", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("first", ((TextResourceContents)result.Contents[0]).Text);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = "resource://Hello?arg2=42" } }, new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = "resource://mcp/Hello?arg2=42" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text);
result = await t.ReadAsync( result = await t.ReadAsync(
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = "resource://Hello?arg1=first&arg2=42" } }, new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = "resource://mcp/Hello?arg1=first&arg2=42" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("first42", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("first42", ((TextResourceContents)result.Contents[0]).Text);
@ -325,7 +354,7 @@ public partial class McpServerResourceTests
}, new() { Name = "Test" }); }, new() { Name = "Test" });
var result = await resource.ReadAsync( var result = await resource.ReadAsync(
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } }, new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text);
@ -376,11 +405,11 @@ public partial class McpServerResourceTests
Mock<IMcpServer> mockServer = new(); Mock<IMcpServer> mockServer = new();
await Assert.ThrowsAnyAsync<ArgumentException>(async () => await resource.ReadAsync( await Assert.ThrowsAnyAsync<ArgumentException>(async () => await resource.ReadAsync(
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } }, new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
TestContext.Current.CancellationToken)); TestContext.Current.CancellationToken));
var result = await resource.ReadAsync( var result = await resource.ReadAsync(
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Services = services, Params = new() { Uri = "resource://Test" } }, new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Services = services, Params = new() { Uri = "resource://mcp/Test" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text);
@ -402,7 +431,7 @@ public partial class McpServerResourceTests
}, new() { Services = services, Name = "Test" }); }, new() { Services = services, Name = "Test" });
var result = await resource.ReadAsync( var result = await resource.ReadAsync(
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = "resource://Test" } }, new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = "resource://mcp/Test" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text); Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text);
@ -436,7 +465,7 @@ public partial class McpServerResourceTests
return new ReadResourceResult { Contents = new List<ResourceContents> { new TextResourceContents { Text = "hello" } } }; return new ReadResourceResult { Contents = new List<ResourceContents> { new TextResourceContents { Text = "hello" } } };
}, new() { Name = "Test" }); }, new() { Name = "Test" });
var result = await resource.ReadAsync( var result = await resource.ReadAsync(
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } }, new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Single(result.Contents); Assert.Single(result.Contents);
@ -453,7 +482,7 @@ public partial class McpServerResourceTests
return new TextResourceContents { Text = "hello" }; return new TextResourceContents { Text = "hello" };
}, new() { Name = "Test", SerializerOptions = JsonContext6.Default.Options }); }, new() { Name = "Test", SerializerOptions = JsonContext6.Default.Options });
var result = await resource.ReadAsync( var result = await resource.ReadAsync(
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } }, new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Single(result.Contents); Assert.Single(result.Contents);
@ -474,7 +503,7 @@ public partial class McpServerResourceTests
]; ];
}, new() { Name = "Test" }); }, new() { Name = "Test" });
var result = await resource.ReadAsync( var result = await resource.ReadAsync(
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } }, new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal(2, result.Contents.Count); Assert.Equal(2, result.Contents.Count);
@ -492,7 +521,7 @@ public partial class McpServerResourceTests
return "42"; return "42";
}, new() { Name = "Test" }); }, new() { Name = "Test" });
var result = await resource.ReadAsync( var result = await resource.ReadAsync(
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } }, new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Single(result.Contents); Assert.Single(result.Contents);
@ -509,7 +538,7 @@ public partial class McpServerResourceTests
return new List<string> { "42", "43" }; return new List<string> { "42", "43" };
}, new() { Name = "Test", SerializerOptions = JsonContext6.Default.Options }); }, new() { Name = "Test", SerializerOptions = JsonContext6.Default.Options });
var result = await resource.ReadAsync( var result = await resource.ReadAsync(
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } }, new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal(2, result.Contents.Count); Assert.Equal(2, result.Contents.Count);
@ -527,7 +556,7 @@ public partial class McpServerResourceTests
return new DataContent(new byte[] { 0, 1, 2 }, "application/octet-stream"); return new DataContent(new byte[] { 0, 1, 2 }, "application/octet-stream");
}, new() { Name = "Test" }); }, new() { Name = "Test" });
var result = await resource.ReadAsync( var result = await resource.ReadAsync(
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } }, new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Single(result.Contents); Assert.Single(result.Contents);
@ -549,7 +578,7 @@ public partial class McpServerResourceTests
}; };
}, new() { Name = "Test", SerializerOptions = JsonContext6.Default.Options }); }, new() { Name = "Test", SerializerOptions = JsonContext6.Default.Options });
var result = await resource.ReadAsync( var result = await resource.ReadAsync(
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } }, new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal(2, result.Contents.Count); Assert.Equal(2, result.Contents.Count);