# `AttestoMCP.Metadata`
[🔗](https://github.com/XukuLLC/attesto_mcp/blob/v1.0.5/lib/attesto_mcp/metadata.ex#L1)

Builders for OAuth metadata used by HTTP MCP authorization.

MCP HTTP servers that require authorization act as OAuth protected resources.
The MCP authorization spec points clients at RFC 9728 protected-resource
metadata first, then at RFC 8414 authorization-server metadata. This module
builds those documents without coupling the package to any MCP server SDK.

## Origin pinning behind a proxy

The `resource` identifier and the default `authorization_servers` are origin
values: a client reads them to learn where it is and where to get a token.
By default they are derived from the live request connection
(`conn.scheme`/`conn.host`/`conn.port`), which is correct for a simple
single-host deployment.

Behind a TLS-terminating reverse proxy that derivation is both fragile and a
spoofing vector: `conn.scheme` is often `http`, `conn.host` is whatever
`X-Forwarded-*` rewriting produced, and an attacker who can set
`X-Forwarded-Host` could make the metadata advertise an attacker-controlled
`resource` or authorization server. A FAPI-grade deployment should pin both
instead of trusting the request:

  * The **`resource`** identifier and the well-known metadata URL use
    `resolve_origin/2` - the *resource server* origin, pinned via `:base_url`
    or `:origin` (a `String.t()`, a `(Plug.Conn.t() -> String.t())` callback,
    or a `{module, fun}` / `{module, fun, args}` tuple), falling back to the
    live request origin.
  * **`authorization_servers`** defaults to the `:config` issuer (or an
    explicit `:issuer` string) when one is supplied - the authorization server
    the host already trusts for token verification, advertised verbatim - and
    otherwise to the resource origin. The issuer is deliberately NOT used as
    the `resource` origin: RFC 9728 keeps the protected resource and its
    authorization server distinct, and in a FAPI deployment they are usually
    different hosts.

Either can still be overridden outright with an explicit `:resource` /
`:authorization_servers` opt. See `guides/proxy_origin.md` for the recipe.

# `append_resource_metadata`

```elixir
@spec append_resource_metadata(String.t(), String.t()) :: String.t()
```

Append `resource_metadata` to a `WWW-Authenticate` challenge.

# `authorization_server`

```elixir
@spec authorization_server(
  Attesto.Config.t(),
  keyword()
) :: %{required(String.t()) =&gt; term()}
```

Build the authorization-server metadata document by delegating to Attesto.

# `protected_resource`

```elixir
@spec protected_resource(keyword()) :: %{required(String.t()) =&gt; term()}
```

Build an RFC 9728 protected-resource metadata document.

Required options:

  * `:resource` - the protected resource identifier, usually the canonical
    MCP server URI such as `"https://mcp.example.com/mcp"`.
  * `:authorization_servers` - a non-empty list of issuer identifiers.

Common options include `:scopes_supported`, `:bearer_methods_supported`,
`:dpop_signing_alg_values_supported`, and
`:tls_client_certificate_bound_access_tokens`.

# `protected_resource`

```elixir
@spec protected_resource(Plug.Conn.t(), String.t(), keyword()) :: %{
  required(String.t()) =&gt; term()
}
```

Build protected-resource metadata from a Plug connection and resource path.

`resource_path` is the path of the MCP endpoint, for example `"/mcp"` or
`"/mcp/admin"`. The `resource` identifier is `resolve_origin/2` (the resource
server origin, pinned via `:base_url`/`:origin`) joined with that path.
`:authorization_servers` defaults to the `:config` issuer (or an explicit
`:issuer` string) when one is given - the trusted authorization server,
advertised verbatim - and otherwise to the resource origin.

An explicit `:resource` or `:authorization_servers` opt overrides the derived
value, and short-circuits its derivation: a pinned origin / issuer callback is
not invoked (and cannot fail) for a value the host supplied outright.

# `protected_resource_url`

```elixir
@spec protected_resource_url(String.t(), String.t()) :: String.t()
@spec protected_resource_url(Plug.Conn.t(), String.t()) :: String.t()
```

Build the well-known metadata URL for an MCP resource path.

    iex> AttestoMCP.Metadata.protected_resource_url("https://mcp.example.com", "/mcp")
    "https://mcp.example.com/.well-known/oauth-protected-resource/mcp"

A Plug connection can also be passed as the first argument.

# `protected_resource_url`

```elixir
@spec protected_resource_url(Plug.Conn.t(), String.t(), keyword()) :: String.t()
```

Build the well-known metadata URL, resolving the origin via `resolve_origin/2`.

Pass `:base_url`/`:origin` in `opts` to pin the resource origin (so the
challenge URL matches a pinned `resource`); with no such opt the live request
origin is used.

# `resolve_origin`

```elixir
@spec resolve_origin(
  Plug.Conn.t(),
  keyword()
) :: String.t()
```

Resolve the *resource server* origin used to build the `resource` identifier
and the well-known metadata URL.

This is the protected resource's own origin (RFC 9728 `resource`), which is
distinct from the authorization server (`authorization_servers` / the
`Attesto.Config` issuer) - the two are often different hosts in a FAPI
deployment, so the issuer is NOT used here. Precedence:

  1. an explicit `:base_url` or `:origin` opt - a `String.t()`, a
     `(Plug.Conn.t() -> String.t())` callback, or a `{module, fun}` /
     `{module, fun, args}` tuple (the conn is prepended to `args`), resolved
     at request time;
  2. the live request origin (`conn.scheme`/`conn.host`/`conn.port`).

A blank or non-binary pin is treated as "not configured" and falls back to the
request origin; a trailing slash is trimmed so the result joins cleanly with a
resource path.

# `resource_identifier`

```elixir
@spec resource_identifier(Plug.Conn.t(), String.t(), keyword()) :: String.t()
```

The canonical RFC 8707 / RFC 9728 resource identifier for this protected
resource: the resolved origin joined with the resource path (for example
`"https://mcp.example.com/mcp"`).

This is the SINGLE source for the value that must agree across the chain — the
RFC 9728 metadata `resource` field, the RFC 8707 `resource` a client requests,
the `aud` the authorization server mints, and the audience the resource-server
plug validates. Driving the audience check and the advertised metadata from
this one function is what makes them consistent by construction (a host cannot
configure them into disagreement). Origin precedence is `resolve_origin/2`'s.

# `resource_metadata_param`

```elixir
@spec resource_metadata_param(String.t()) :: String.t()
```

Build the `WWW-Authenticate` auth-param value for RFC 9728 metadata discovery.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
