Canonical resource identifiers (RFC 8707 / RFC 9728).
An MCP mount's resource URI is the audience its access tokens are bound to.
Getting the comparison wrong is the whole confused-deputy problem: a token
minted for https://host/mcp must not open https://host/mcp/learning.
So the contract here is byte-exact matching, and normalization is
limited to the handful of transformations the RFCs declare
scheme-insignificant:
- scheme lowercased, and restricted to
http/https - host lowercased (DNS is case-insensitive)
- the default port for the scheme dropped
Everything else is left alone. In particular there is no trailing-slash
coercion — …/mcp and …/mcp/ are different resources, because a client
that asked for one must not be handed the other, and because the protected
resource metadata document has to byte-match whatever the user typed.
Fragments are rejected (RFC 8707 §2 forbids them); a fragment would let two distinct strings name one resource.
iex> Noizu.MCP.Auth.Resource.normalize("HTTPS://App.Example.COM:443/mcp/learning")
{:ok, "https://app.example.com/mcp/learning"}
iex> Noizu.MCP.Auth.Resource.equal?("https://x/mcp", "https://x/mcp/")
false
Summary
Functions
Build a mount's canonical resource from an issuer origin and a path.
True when both sides normalize to the same canonical URI.
True when resource equals the single configured resource, or is a member of
the configured list.
Normalize a resource URI, or {:error, :invalid_resource} when it is not a
usable absolute identifier.
Normalize or raise. For mount configuration, where a bad resource URI is a deployment error that should surface at boot rather than as a silent 401.
Types
@type error() :: :invalid_resource
@type t() :: String.t()
Functions
Build a mount's canonical resource from an issuer origin and a path.
The issuer is an origin with no path (the design decision that collapses the RFC 8414 path-insertion ambiguity), so this is a plain concatenation — the path is not rewritten, only required to be absolute.
iex> Noizu.MCP.Auth.Resource.build("https://app.example.com", "/mcp/learning")
{:ok, "https://app.example.com/mcp/learning"}
True when both sides normalize to the same canonical URI.
True when resource equals the single configured resource, or is a member of
the configured list.
A list is supported for issuer/mount migrations. It never widens what a token reaches — each entry still has to match byte-exactly.
Normalize a resource URI, or {:error, :invalid_resource} when it is not a
usable absolute identifier.
Normalize or raise. For mount configuration, where a bad resource URI is a deployment error that should surface at boot rather than as a silent 401.