# `ConduitMcp.Client.OAuth.Loopback`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.11.0/lib/conduit_mcp/client/oauth/loopback.ex#L2)

Interactive authorization for CLI and desktop apps: a one-shot callback
server on `127.0.0.1` (RFC 8252 §7.3 loopback redirect). Compiled only
with `:finch`.

    {:ok, tokens} =
      ConduitMcp.Client.OAuth.Loopback.run(:docs,
        open: fn url -> System.cmd("open", [url]) end,
        challenge: challenge
      )

`run/2`:

  1. starts Bandit on `127.0.0.1` (port `0` by default: any free port,
     which RFC 8252 lets the authorization server accept for loopback
     redirect URIs; give `:port` if yours needs a fixed one) serving
     exactly one request on `:path`;
  2. builds the authorization URL with redirect URI
     `http://127.0.0.1:<port><path>` (`ConduitMcp.Client.OAuth.authorize_url/2`);
  3. calls the host's `:open` function with the URL - **the library
     never launches a browser itself**: the host decides how (`open`,
     `xdg-open`, printing the URL);
  4. waits (`:timeout`, default 5 minutes) for the browser's redirect,
     answers it with a short page, and stops the server;
  5. completes the flow with `ConduitMcp.Client.OAuth.handle_callback/3`.

The server binds the loopback address only and answers a single
callback (OA-11): any later request gets `410 Gone`, and the listener is
gone right after. The pending authorization never leaves the calling
process, so `state` is used once by construction.

`start/1` and `await/2` are the two halves of step 1 and 4, for a host
that runs the flow itself; `await/2` returns the callback's query
parameters.

# `t`

```elixir
@type t() :: %ConduitMcp.Client.OAuth.Loopback{
  redirect_uri: String.t(),
  ref: reference(),
  server: pid()
}
```

# `await`

```elixir
@spec await(t(), timeout()) :: {:ok, map()} | {:error, :timeout}
```

Waits up to `timeout` ms for the callback, then stops the server.
Returns the callback's query parameters, or `{:error, :timeout}`.

# `run`

```elixir
@spec run(ConduitMcp.Client.OAuth.client_or_config(), keyword()) ::
  {:ok, ConduitMcp.Client.OAuth.Tokens.t()} | {:error, term()}
```

Runs a whole interactive authorization for `client_or_config` (see the
moduledoc) and returns the stored tokens.

## Options

  * `:open` (required) - a one-arity function given the authorization
    URL.
  * `:port`, `:path` - see `start/1`.
  * `:timeout` - how long to wait for the user, default 5 minutes.
  * `:challenge`, `:scopes`, `:principal` - passed to
    `ConduitMcp.Client.OAuth.authorize_url/2`.

# `start`

```elixir
@spec start(keyword()) :: {:ok, t()} | {:error, term()}
```

Starts the one-shot callback server.

## Options

  * `:port` - default `0` (any free port).
  * `:path` - the callback path, default `"/callback"`.

# `stop`

```elixir
@spec stop(t()) :: :ok
```

Stops the callback server (idempotent).

---

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