Skip to content

servant-auth-server: support MultiVerb endpoints behind Auth - #1922

Merged
ysangkok merged 2 commits into
haskell-servant:masterfrom
nuttycom:feat/auth-multiverb
Nov 6, 2026
Merged

ysangkok merged 2 commits into
haskell-servant:masterfrom
nuttycom:feat/auth-multiverb

Conversation

@nuttycom

@nuttycom nuttycom commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

An API with a MultiVerb endpoint under Auth does not compile today: Auth adds its Set-Cookie headers to every endpoint below it through AddSetCookieApi and AddSetCookies, and neither has an instance for MultiVerb.

This adds them.

  • Each response of a MultiVerb endpoint gets an optional Set-Cookie header: WithHeaders '[OptHeader (Header "Set-Cookie" SetCookie)] (ResponseType a, Maybe SetCookie) a.
  • The handler is written as before; Auth pairs its result with the cookie (AddSetCookies), one layer per cookie, like the Verb case.
  • The result type of the cookied endpoint depends on its number of responses:
    • one response: (r, Maybe SetCookie). servant's own AsUnion '[r] a instance converts it, which is also why a wrapper type cannot be used here: an AsUnion bs (Wrapper r) instance would overlap with AsUnion '[r] a.
    • several responses: WithSetCookie as r, whose AsUnion instance converts r with AsUnion as r and moves the cookie into whichever response the handler gives (AddSetCookieUnion).

sop-core becomes a direct dependency of servant-auth-server, for NS and I; servant already depends on it.

Tests

Servant.Auth.ServerSpec gains routes behind Auth, with examples that check the cookie names Auth sets (session and XSRF, so every endpoint stacks two Set-Cookie layers):

  • a two-response MultiVerb (RespondEmpty 204 / Respond 200 Int, handler result Maybe Int), on both of its responses;
  • a MultiVerb1;
  • a MultiVerb1 whose response has its own Blah header, which is kept;
  • a two-response MultiVerb whose 200 response sets its own Set-Cookie, which is kept with its value;
  • a MultiVerb whose handler answers unauthenticated requests itself: the 401 response keeps its own header and has no Set-Cookie header, and the authenticated 200 response has both cookies.

cabal test servant-auth-server:spec with GHC 9.12: 39 examples, 0 failures, 2 pending (pending before this change).

Filed with Claude Code assistance.

@nuttycom
nuttycom force-pushed the feat/auth-multiverb branch from 00553c2 to b2cfaa7 Compare October 13, 2026 14:40
nuttycom added a commit to nuttycom/mcp that referenced this pull request Oct 13, 2026
The Streamable HTTP transport requires a server to answer a POST that
carries only a notification or a response with 202 Accepted and no body.
The POST route answered every message with a 200 event stream: a
notification got an invented `{"method":"ok"}` notification, and a
response got an empty stream. Clients that follow the specification,
such as Codex, close the connection after `notifications/initialized`.

The POST route of `MCPAPI` and `SimpleHTTPAPI` is now a `MultiVerb` over
`MCPPostResponses`: 202 with no body for a notification or a response,
and 200 with the JSON-RPC messages as Server-Sent Events for a request.
A response to a request the server never sent gets 400, instead of an
error inside a 200 stream.

servant-auth-server has no instances for `MultiVerb`, so the internal
module `MCP.Server.HTTP.AuthMultiVerb` gives them until a release
includes them
(haskell-servant/servant#1922). The `servant-auth-server < 0.4.11`
bound keeps the two from clashing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An API with a `MultiVerb` endpoint under `Auth` did not compile:
`AddSetCookieApi` and `AddSetCookies` had no instances for `MultiVerb`.

Each response of a `MultiVerb` endpoint now gets an optional `Set-Cookie`
header. The handler is written as before; `Auth` pairs its result with
the cookie. An endpoint with one response takes the pair directly,
because servant's own `AsUnion '[r] a` instance converts it. An endpoint
with several responses takes `WithSetCookie as r`, whose `AsUnion`
instance moves the cookie into whichever response the handler gives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nuttycom
nuttycom force-pushed the feat/auth-multiverb branch from b2cfaa7 to 1c0095a Compare October 16, 2026 05:36
@nuttycom
nuttycom requested a review from LaurentRDC October 16, 2026 16:05

@LaurentRDC LaurentRDC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very close, just one more check for completeness

… Blah header

The refusal response carries a Blah header. Check that the success
response to an authenticated request does not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@LaurentRDC
LaurentRDC requested a review from ysangkok November 5, 2026 02:25
@ysangkok
ysangkok merged commit 718e07b into haskell-servant:master Nov 6, 2026
10 checks passed
@nuttycom
nuttycom deleted the feat/auth-multiverb branch November 11, 2026 20:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants