Snodo.OTP.Proxy (snodo_otp v0.4.3)

Copy Markdown View Source

A local GenServer that stands in for a GenServer behind an MCP endpoint. Application code keeps GenServer.call/3 and GenServer.cast/2; each becomes a tools/call chosen by the contract.

Requests go out one at a time, in the order the proxy receives them, and each waits for its result before the next is sent. That keeps the ordering OTP gives a sender and a GenServer: casts arrive in order, and a call sees every cast sent before it. The remote GenServer handles one message at a time anyway; the cost is that requests do not overlap on the wire.

Failure semantics follow OTP where the wire allows it:

  • The caller's own GenServer.call timeout decides how long the caller waits, and a result that arrives after the caller gave up is dropped, as a late reply is in OTP. The request itself is not cancelled: it holds the single in-flight slot until the remote side answers or :request_timeout expires, so later requests wait behind it.
  • A call the remote side timed out (the adapter's :timeout, or the proxy's :request_timeout, default 60,000 ms) is answered {:error, :timeout}, so a caller with an :infinity timeout is not left waiting.
  • A call to a remote target that is not there (GenServer target unavailable) stops the proxy with :noproc, so a Process.monitor/1 on the proxy delivers DOWN and a supervisor can restart it. A cast to it is dropped, as a cast to a dead name is in OTP. With on_unavailable: :error the call is answered {:error, :noproc} instead and the proxy keeps running.
  • At most :max_queue requests (default 1,000) wait behind the one in flight. A call beyond that is answered {:error, :queue_full} and a cast is dropped.
  • A request whose process crashes, for example on arguments JSON cannot encode, is answered {:error, {:request_failed, reason}}, and later requests still run.
  • A reply decode_reply/2 does not match is answered {:error, {:decode_failed, name, json}}. encode_call/1 and encode_cast/1 run on the caller's message in the proxy, as a GenServer callback does, so a raise there stops the proxy.
  • A connection failure stops the proxy with {:nodedown, url}. An HTTP error status, such as 401 or 404, is answered {:error, error} and the proxy keeps running.
  • A message the contract does not know stops the proxy with {:function_clause, message}, which is what an unmatched callback clause does to a GenServer.

Options: :url and :contract (required), :name, :headers, :request_timeout, :max_queue, :on_unavailable (:stop, the default, or :error), and :connect, a keyword list merged over the defaults passed to Snodo.Client.connect/2 (protocol: "2026-07-28", timeout: 15_000), for example connect: [token_provider: provider].

What it cannot give: a pid for the remote process, links to it, or any DOWN that is not provoked by a call. Those need a subscription stream.

Summary

Functions

Returns a specification to start this module under a supervisor.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

start_link(opts)