Current section

Files

Jump to
corex lib mcp plug.ex
Raw

lib/mcp/plug.ex

defmodule Corex.MCP do
@moduledoc false
@behaviour Plug
require Logger
@impl true
def init(opts) when is_list(opts) do
maybe_silence_mcp_server_logs()
%{
allow_remote_access: Keyword.get(opts, :allow_remote_access, false)
}
end
def init(%{} = opts) do
maybe_silence_mcp_server_logs()
Map.merge(%{allow_remote_access: false}, opts)
end
defp maybe_silence_mcp_server_logs do
if Application.get_env(:corex, :debug) do
:ok
else
Logger.put_module_level(Corex.MCP.Server, :none)
end
end
@impl true
def call(%Plug.Conn{path_info: ["corex" | rest]} = conn, config) do
conn
|> validate!()
|> Plug.Conn.put_private(:corex_mcp_config, config)
|> Plug.forward(rest, Corex.MCP.Router, [])
|> Plug.Conn.halt()
end
def call(conn, _opts) do
conn
|> Plug.Conn.register_before_send(fn conn ->
conn
|> maybe_rewrite_csp()
|> Plug.Conn.delete_resp_header("x-frame-options")
end)
end
defp validate!(conn) do
if live_reload_enabled?(conn) or request_body_parsed?(conn) do
raise "plug Corex.MCP is runnning too late, after the request body has been parsed. " <>
"Make sure to place \"plug Corex.MCP\" before the \"if code_reloading? do\" block"
end
conn
end
defp live_reload_enabled?(conn) do
match?(%{phoenix_live_reload: true}, conn.private)
end
defp request_body_parsed?(conn) do
not match?(%Plug.Conn.Unfetched{}, conn.body_params)
end
defp maybe_rewrite_csp(conn) do
case Plug.Conn.get_resp_header(conn, "content-security-policy") do
[csp | _] ->
csp = rewrite_csp(csp)
Plug.Conn.put_resp_header(conn, "content-security-policy", csp)
_ ->
conn
end
end
defp rewrite_csp(csp) do
policy_directives = String.split(csp, ";", trim: true)
for policy_directive <- policy_directives,
policy_directive = String.trim(policy_directive),
not String.starts_with?(policy_directive, "frame-ancestors") do
rewrite_csp_directive(policy_directive)
end
|> Enum.join("; ")
end
defp rewrite_csp_directive(policy_directive) do
case String.split(policy_directive, " ", parts: 2) do
["script-src", directives] ->
script_src_with_unsafe_eval(directives)
[policy, directives] ->
"#{policy} #{directives}"
[leftover] ->
leftover
end
end
defp script_src_with_unsafe_eval(directives) do
case :binary.match(directives, "'unsafe-eval'") do
:nomatch -> "script-src 'unsafe-eval' #{directives}"
_ -> "script-src #{directives}"
end
end
end