Packages
fnord
0.9.13
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/transport.ex
defmodule MCP.Transport do
@moduledoc "Convert MCP server config into Hermes transport tuples and helpers for OAuth header injection"
@typedoc "Hermes transport tuple"
@type t ::
{:stdio, keyword()}
| {:streamable_http, keyword()}
| {:websocket, keyword()}
@doc "Convert server config map into a Hermes transport tuple"
@spec map(String.t(), map()) :: {atom(), keyword()}
def map(_server, %{"transport" => "stdio"} = cfg) do
{:stdio,
[
command: MCP.STDIOWrapper.script_path!(),
args: [cfg["command"] | cfg["args"] || []],
env: cfg["env"] || %{}
]}
end
def map(server, %{"transport" => "http"} = cfg) do
headers = merge_oauth_header(server, cfg, cfg["headers"] || %{})
opts = [
base_url: cfg["base_url"],
headers: headers
]
# Add optional mcp_path if specified (default is "/mcp" in Hermes)
opts =
case Map.get(cfg, "mcp_path") do
path when is_binary(path) -> Keyword.put(opts, :mcp_path, path)
_ -> opts
end
{:streamable_http, opts}
end
def map(server, %{"transport" => "websocket"} = cfg) do
headers = merge_oauth_header(server, cfg, cfg["headers"] || %{})
{:websocket,
[
base_url: cfg["base_url"],
headers: headers
]}
end
def map(server, %{"transport" => transport} = _cfg) do
UI.error("""
Invalid transport '#{transport}' for MCP server '#{server}'.
Valid transports are: "stdio", "http", "websocket"
This error often occurs with outdated config values (e.g., "streamable_http" from older versions).
To fix:
1. Manually edit ~/.fnord/settings.json and change the transport value to a valid one
2. Or remove the server config and re-add it: fnord config mcp remove #{server} && fnord config mcp add #{server} [options]
""")
raise ArgumentError, "Invalid transport '#{transport}' for MCP server '#{server}'"
end
def map(server, cfg) do
UI.error("""
Missing or invalid transport configuration for MCP server '#{server}'.
Config received: #{inspect(cfg)}
To fix:
1. Manually edit ~/.fnord/settings.json to add a valid "transport" field
2. Or remove the server config and re-add it: fnord config mcp remove #{server} && fnord config mcp add #{server} [options]
""")
raise ArgumentError, "Missing transport configuration for MCP server '#{server}'"
end
# Inject OAuth Authorization header if server has oauth config and valid credentials
defp merge_oauth_header(server, cfg, base_headers) do
case Map.get(cfg, "oauth") do
nil ->
base_headers
oauth_cfg when is_map(oauth_cfg) ->
case MCP.OAuth2.Bridge.authorization_header(server, cfg) do
{:ok, oauth_headers} ->
# Convert list of tuples to map and merge with base headers
oauth_map = Map.new(oauth_headers)
Map.merge(base_headers, oauth_map)
{:error, reason} when reason in [:no_credentials, :not_found] ->
UI.warn(
"MCP server '#{server}' has OAuth config but no credentials. To fix",
"fnord config mcp login #{server}"
)
base_headers
{:error, :no_refresh_token} ->
UI.warn(
"MCP server '#{server}' has expired credentials with no refresh token. To fix",
"fnord config mcp login #{server}"
)
base_headers
{:error, reason} ->
UI.warn("Failed to get OAuth token for MCP server '#{server}'", reason)
base_headers
end
end
end
end