Skip to main content

lib/noizu/mcp/auth/protected_resource_metadata_plug.ex

if Code.ensure_loaded?(Plug.Conn) do
  defmodule Noizu.MCP.Auth.ProtectedResourceMetadataPlug do
    @moduledoc """
    Serves the RFC 9728 protected-resource metadata document MCP clients use
    to discover your authorization server:

        # Phoenix router (path is fixed by RFC 9728)
        forward "/.well-known/oauth-protected-resource",
                Noizu.MCP.Auth.ProtectedResourceMetadataPlug,
                resource: "https://api.example.com/mcp",
                authorization_servers: ["https://auth.example.com"]

    Options: `:resource` (required), `:authorization_servers` (required),
    `:scopes_supported`, `:bearer_methods_supported`, `:extra` (map merged in).
    """

    @behaviour Plug
    import Plug.Conn

    @impl Plug
    def init(opts) do
      document =
        %{
          "resource" => Keyword.fetch!(opts, :resource),
          "authorization_servers" => Keyword.fetch!(opts, :authorization_servers)
        }
        |> put_opt(opts, :scopes_supported, "scopes_supported")
        |> put_opt(opts, :bearer_methods_supported, "bearer_methods_supported")
        |> Map.merge(Keyword.get(opts, :extra, %{}))

      %{body: Jason.encode!(document)}
    end

    @impl Plug
    def call(%{method: "GET"} = conn, %{body: body}) do
      conn
      |> put_resp_content_type("application/json")
      |> send_resp(200, body)
    end

    def call(conn, _opts), do: send_resp(conn, 405, "Method not allowed")

    defp put_opt(document, opts, key, json_key) do
      case Keyword.fetch(opts, key) do
        {:ok, value} -> Map.put(document, json_key, value)
        :error -> document
      end
    end
  end
end