Wymcp. Plugs. Pipeline
(Wymcp v0.6.2)
View Source
The POST plug chain: every check and stage a POST passes through, in the one order that makes each stage's precondition true.
Wymcp.Plugs.OriginCheck # before anything is parsed
parse_body # Plug.Parsers, JSON
Wymcp.Plugs.Classify # message kind
Wymcp.Plugs.Auth # authentication
Wymcp.Plugs.SingletonHeaders # singleton-header cardinality
Wymcp.Plugs.Era # era classification
Wymcp.Plugs.ProtocolFields # modern-lane enforcement
Wymcp.Plugs.HeaderBinding # modern-lane header binding
Wymcp.Plugs.Session # legacy-lane session resolution
Wymcp.Plugs.Validate # MCP schema
Wymcp.Plugs.Dispatch # method → its answering moduleWhy that order. The origin check runs before anything is parsed because
nothing has validated Origin when it runs — Wymcp.Router's wire-check
invariant, which is also where the wire checks and their order are stated.
Parsing and classification sit between the origin check and the remaining
wire checks so those checks' rejections can carry the body's id and know
the message kind. Wymcp.RouterTest pins that placement end to end — its
authentication describe proves the tag is present when the auth check,
the first consumer, reads it, and its singleton-header check describe
proves the same at the next site; Wymcp.Plugs.AuthTest and
Wymcp.Plugs.SingletonHeadersTest call their plug directly rather than
routing a request, so neither sees the order. Era classification runs
after the singleton-header check, which guarantees at most one
Mcp-Session-Id for it to read. The header-binding check runs after
Wymcp.Plugs.ProtocolFields, which has just proven the body's protocol
version is a string naming a served revision — so its protocol-version row
has a known-good body value to compare against, and a malformed _meta
meets its own -32602 first. Its other rows read body fields nothing has
validated yet, and answer none of them: a body value with no header
spelling binds nothing, so the plug or method that owns the defect still
answers it. That plug's own moduledoc carries the rule. Validation runs
last before dispatch, so the wire and session checks answer first.
The chain is gated, not derived. The wire checks cannot be spliced in as
one list — parsing and classification interleave them — so this module
writes the order by hand and exposes it through chain/0, and
Wymcp.WireCheckInvariantTest holds it to the router's list, in two cells
that both read the chain only as far as Wymcp.Plugs.Session — the point
past which a request has touched session state: every wire check is in
that prefix, in list order, and every other plug in it is declared
body-bound in body_bound_plugs/0, with the reason it runs on POST only.
A plug added ahead of Wymcp.Plugs.Session therefore joins the wire-check
list, and so runs on GET and DELETE, or declares why it cannot; a wire
check moved past Wymcp.Plugs.Session leaves that prefix, and fails the
first of the two cells. chain/0 reads Plug.Builder's accumulated
@plugs, which is stored newest-first as {plug, opts, guards} tuples,
so the accessor reverses it and keeps the first element; a function plug
appears as its bare atom, exactly as it is declared.
Body parsing is an inline function rather than a plug so that a malformed
body answers in wymcp's own dialect: Plug.Parsers.ParseError is rescued and
answered HTTP 400 plus -32700 with data.reason, instead of propagating as
Plug's own error.
GET and DELETE do not run this pipeline. Wymcp.Router runs the wire
checks in those route bodies, straight from its list, speaking the
plain-JSON dialect.
Summary
Functions
Callback implementation for Plug.call/2.
Callback implementation for Plug.init/1.