AttestoMCP.Plug.ProtectResource (AttestoMCP v0.5.2)

Copy Markdown View Source

Protect an HTTP MCP endpoint in one plug.

The MCP authorization spec treats a protected HTTP MCP server as an OAuth resource server (RFC 9728). Guarding such an endpoint correctly takes two ordered steps: authenticate the access token (and any DPoP/mTLS sender constraint), then enforce the scopes the route requires. ProtectResource composes AttestoMCP.Plug.Authenticate followed by AttestoMCP.Plug.RequireScopes into a single, correctly ordered, halt-respecting pipeline so the host does not hand-wire and re-order the two plugs (and the WWW-Authenticate resource_metadata challenge) on every route.

plug AttestoMCP.Plug.ProtectResource,
  config: &MyApp.Attesto.config/0,
  replay_check: &MyApp.DPoPReplay.check_and_record/2,
  resource: "/mcp",
  scopes: [AttestoMCP.Scopes.tools_call()]

This is exactly equivalent to:

plug AttestoMCP.Plug.Authenticate,
  config: &MyApp.Attesto.config/0,
  replay_check: &MyApp.DPoPReplay.check_and_record/2,
  resource_path: "/mcp"

plug AttestoMCP.Plug.RequireScopes,
  scopes: [AttestoMCP.Scopes.tools_call()]

Options

  • :scopes (or :scope) - the scope(s) the route requires, forwarded to AttestoMCP.Plug.RequireScopes. At least one scope is required.
  • :resource (or :resource_path) - the MCP endpoint path, for example "/mcp" or "/mcp/brokers". It drives the RFC 9728 resource_metadata auth-param appended to WWW-Authenticate challenges, derived from the live request origin via AttestoMCP.Metadata.protected_resource_url/2. Both names mean the same thing; :resource reads naturally here while :resource_path matches AttestoMCP.Plug.Authenticate.

Every other option is passed through to AttestoMCP.Plug.Authenticate: :config, :replay_check, :nonce_check, :nonce_issue, :cert_der, :htu, :credential_from_conn, :send_error, :www_authenticate, :no_store, :principal, :principal_key, :claims_key, :scopes_key, :sender_key, and :resource_metadata_url. The transport hooks (:send_error, :www_authenticate) and the assign keys (:claims_key, :scopes_key) are also shared with AttestoMCP.Plug.RequireScopes so a scope rejection renders through the same host-controlled error envelope.