Media types are case-insensitive in HTTP, so the takeover guard compared
them lower-cased, but the SDK's transport matches Accept as a
case-sensitive substring. A header such as Application/JSON passed the
guard, closed the live session, and then got 406 from the SDK. The guard
now also requires the SDK's Accept substrings and uses its exported
isJsonContentType, so it only replaces a session the SDK will accept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A comma or semicolon inside a quoted parameter is not a separator, so a
header such as application/json;note=", text/event-stream;x=1" holds a
single range. The takeover guard split on every comma and found a
text/event-stream range the client never sent. Ranges and their
parameters are now split outside quoted strings, and q is read from the
parsed parameters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A browser on the listener's own page sends Origin with the scheme and the
bound port. The SDK validator parses the Origin and compares its hostname
with the loopback allowlist, so the port never takes part; this test keeps
that true for the MCP endpoint and for composed routes, under 127.0.0.1
and localhost.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses a thirteenth automated review finding: `application/json;q=0`
explicitly refuses JSON, so it no longer counts toward the exact Accept ranges
a session takeover requires.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses the twelfth automated review finding: bodyUsed only means a body
was disturbed, so a route that read one chunk of an upload left the rest
paused on a keep-alive socket. The close decision now follows the incoming
message's end of stream.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The previous commit added a second successful route call to the regression
but left the single-observation assertion in place.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the eleventh automated review finding: dot segments such as
`/public/../mcp` were canonicalized to `/mcp` before routing. The bridge now
refuses any target whose path differs after parsing, so routing sees exactly
the path the client sent.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the tenth automated review round: the bridge joins an origin-form
request target onto the listener's origin so a network-path target stays a
path and absolute-form targets are refused; only a lone initialize can replace
an open session; and the shutdown hook records completion only after it
returns, so a hook that threw stays eligible for the next close.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the ninth automated review round: a method Fetch refuses, such as
TRACE, is answered with 405 instead of escaping the request guard; the
unread-body decision now comes from the incoming message's own body
declaration, so a GET or HEAD carrying a body closes too; and the regression
uses a keep-alive client and checks that the socket actually closes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the eighth automated review finding: a refused Host or Origin, an
unknown route or the startup 503 answered a POST while its body stayed unread
on the paused bridged stream; such replies now carry Connection: close so an
unread upload cannot keep a socket open.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the fifth automated review round: the instance's shutdown hook runs
exactly once even when the listener closes without a live session; the
initialize probe no longer awaits cancelling its cloned body, which would wait
for the original request to drain; and the node:http bridge passes repeated
Set-Cookie values through as an array.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the fourth automated review round: a sessionless POST replaces a
session only when its Accept header lists application/json and
text/event-stream as exact media ranges, and a session whose transport
shutdown rejected stays owned so the next close retries it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the third automated review round: loopback is classified from the
address the listener bound with the configured name added to the Host and
Origin allowlist; scoped IPv6 literals are refused before binding and a URL
that cannot be formed closes the listener; listener errors reach onerror only
after a successful bind; the MCP runtime keeps its factory owner as the single
owner of instance cleanup through `closeInstance: false`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the second automated review round: a sessionless POST replaces a
session only when its media type proper is application/json, and the server
instance is created after the listener binds so a bind failure leaves nothing
to clean up and a factory failure releases the listener.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Addresses the automated review findings on the PR: refuse requests once the
session slot is closed and join an arming initialize during shutdown; retry
failed close stages instead of replaying the rejection; classify loopback
binds by address class and add the bind's own hostname to the Host and Origin
allowlist; let only a POST with the streamable headers replace an open
session; announce the bound URL at every log level; route `mcp -h --http` to
the HTTP entry.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add rea mcp --http [--host] [--port]: the same server rea speaks over stdio,
served over MCP Streamable HTTP on a loopback listener by default, using the
SDK's web-standard transport already in the pinned @modelcontextprotocol/server
behind a small node:http bridge. One client and one session at a time; DELETE
ends the session without running provider teardown, a new client's initialize
replaces a session left open, foreign Host headers are refused on loopback
binds, and shutdown is signals-only. Stdio stays the default and registered
transport; server.json and client registrations are unchanged.
Refs morluto/rea#4933
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>