Skip to content

Static resources whose URIs differ only in the fragment or user info collapse into one #1970

Open

Description

@chrikrah

Two static resources whose URIs differ only in the fragment collapse into one. WithResources silently drops the second, and reading it returns the first.

Repro at 5221939, in-process over two Pipes:

services.AddMcpServer().WithResources([
    McpServerResource.Create(() => "intro text", new() { UriTemplate = "docs://guide#intro", Name = "intro" }),
    McpServerResource.Create(() => "setup text", new() { UriTemplate = "docs://guide#setup", Name = "setup" }),
]);
// ...McpServer.Create over StreamServerTransport, McpClient.CreateAsync over StreamClientTransport
foreach (var r in await client.ListResourcesAsync()) Console.WriteLine($"listed: {r.Uri}");
var read = await client.ReadResourceAsync("docs://guide#setup");
$ dotnet run -c Release      # net10.0, SDK 10.0.403, ProjectReference to src/ModelContextProtocol at 5221939
listed: docs://guide#intro
read docs://guide#setup -> intro text

Expected: both listed, and the read returns setup text. A server exposing one resource per heading anchor loses every section after the first, with no error. The spec says each resource "is uniquely identified by a URI" (server/resources), and RFC 3986 section 3 puts the fragment in it. Section 3.5 strips it for dereference, not for two entries a server registered.

UriTemplateComparer (UriTemplate.cs:502) says it uses "regular System.Uri equality comparison (host name is case insensitive)", and its tests cover host case only. Uri.Equals also ignores UserInfo and Fragment, so docs://alice@host/x and docs://bob@host/x collide too. McpServerOptionsSetup.cs:63 uses TryAdd, so nothing throws.

I'd keep System.Uri and add && uri1.UserInfo == uri2.UserInfo && uri1.Fragment == uri2.Fragment at line 519:

$ dotnet run -c Release      # same app, with that change at UriTemplate.cs:519
listed: docs://guide#intro
listed: docs://guide#setup
read docs://guide#setup -> setup text
# not run: test suite with the change

The comparer also backs IsMatch (AIFunctionMcpServerResource.cs:325) and the SubscribeToResourceAsync filter (McpClient.Methods.cs:832), so a read or update for docs://guide#x would stop matching a static docs://guide. That fits the spec but changes behavior for clients sending fragments today. Shall I open that pull request with a test in McpServerResourceTests?

Activity

  1. added a commit that references this issue on Nov 2, 2026
    cddf0de
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions