Packages
fnord
0.8.55
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/mcp/oauth2/bridge.ex
defmodule MCP.OAuth2.Bridge do
@moduledoc """
Builds Authorization header for MCP transports.
If token is near expiry, attempts a refresh via `Client` and persists.
"""
alias MCP.OAuth2.{CredentialsStore, Client}
@default_refresh_margin 120
@spec authorization_header(String.t(), map(), keyword()) ::
{:ok, [{String.t(), String.t()}]} | {:error, term()}
def authorization_header(server, cfg, opts \\ []) do
margin = Keyword.get(opts, :refresh_margin, @default_refresh_margin)
with {:ok, toks} <- CredentialsStore.read(server),
{:ok, toks2} <- maybe_refresh(server, cfg, toks, margin),
at when is_binary(at) <- Map.get(toks2, "access_token") do
{:ok, [{"authorization", "Bearer " <> at}]}
else
{:error, reason} -> {:error, reason}
_ -> {:error, :no_credentials}
end
end
defp maybe_refresh(server, cfg, %{"expires_at" => exp} = toks, margin) do
if exp - System.os_time(:second) <= margin do
refresh(server, cfg, toks)
else
{:ok, toks}
end
end
defp maybe_refresh(_server, _cfg, toks, _margin), do: {:ok, toks}
defp refresh(server, cfg, %{"refresh_token" => rt}) when is_binary(rt) and rt != "" do
oauth = Map.get(cfg, "oauth", %{})
# Use configured redirect_uri, or build from redirect_port, or omit if neither present
redirect_uri =
cond do
is_binary(oauth["redirect_uri"]) ->
oauth["redirect_uri"]
is_integer(oauth["redirect_port"]) and oauth["redirect_port"] > 0 ->
"http://localhost:#{oauth["redirect_port"]}/callback"
true ->
nil
end
# Build OAuth config for refresh - need discovery_url, client_id, optional client_secret
oauth_cfg =
%{
discovery_url: oauth["discovery_url"],
client_id: oauth["client_id"],
client_secret: oauth["client_secret"],
scopes: Map.get(oauth, "scopes", [])
}
|> maybe_put_redirect(redirect_uri)
case Client.refresh_token(oauth_cfg, rt) do
{:ok, new} ->
:ok = CredentialsStore.write(server, normalize(new))
{:ok, normalize(new)}
{:error, e} ->
{:error, e}
end
end
defp refresh(_server, _cfg, _toks), do: {:error, :no_refresh_token}
defp maybe_put_redirect(map, nil), do: map
defp maybe_put_redirect(map, uri), do: Map.put(map, :redirect_uri, uri)
defp normalize(%{
access_token: at,
refresh_token: rt,
token_type: tt,
expires_at: exp,
scope: sc
}) do
%{
"access_token" => at,
"refresh_token" => rt,
"token_type" => tt,
"expires_at" => exp,
"scope" => sc
}
end
end